msg_tool\scripts\kirikiri\archive\xp3\crypt/
cx.rs

1use super::super::{FileHashOption, PathHashOption};
2use super::*;
3use crate::ext::atomic::AtomicQuick;
4use crate::ext::mutex::MutexExt;
5use crate::utils::files::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use chacha20::cipher::array::Array;
9use chacha20::hchacha;
10use chacha20::{ChaCha20Legacy, KeyIvInit};
11use msg_tool_macro::{MyDebug, StructUnpack};
12use pelite::PeFile;
13use serde::{Deserializer, Serialize, Serializer, de};
14use sha3::Sha3_224;
15use shake::Shake256;
16use std::collections::HashSet;
17use std::ops::{Deref, DerefMut, Index};
18use std::path::PathBuf;
19use std::sync::atomic::AtomicU64;
20use std::sync::{Mutex, Weak};
21use tjs2dec::{Tjs2File, Tjs2Object};
22
23const S_CTL_BLOCK_SIGNATURE: &[u8] = b" Encryption control block";
24
25macro_rules! base_schema_impl {
26    () => {
27        fn hash_after_crypt(&self) -> bool {
28            AsRef::<BaseSchema>::as_ref(self.as_ref()).hash_after_crypt
29        }
30        fn startup_tjs_not_encrypted(&self) -> bool {
31            AsRef::<BaseSchema>::as_ref(self.as_ref()).startup_tjs_not_encrypted
32        }
33        fn obfuscated_index(&self) -> bool {
34            AsRef::<BaseSchema>::as_ref(self.as_ref()).obfuscated_index
35        }
36    };
37}
38
39#[derive(Debug)]
40pub struct CxEncryption {
41    mask: u32,
42    offset: u32,
43    prolog_order: Vec<u8>,
44    odd_branch_order: Vec<u8>,
45    even_branch_order: Vec<u8>,
46    control_block: Arc<Vec<u32>>,
47    programs: Vec<Box<dyn ICxProgram + Send + Sync>>,
48    program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
49    base: BaseSchema,
50}
51
52trait ICxEncryption: std::fmt::Debug {
53    fn get_base_offset(&self, hash: u32) -> u32;
54    fn inner_decrypt(
55        &self,
56        mut key: u32,
57        mut offset: u64,
58        buffer: &mut [u8],
59        mut pos: usize,
60        mut count: usize,
61    ) -> Result<()> {
62        let base_offset = self.get_base_offset(key);
63        if offset < base_offset as u64 {
64            let base_length = ((base_offset as u64 - offset) as usize).min(count);
65            self.decode(key, offset, buffer, pos, base_length)?;
66            offset += base_length as u64;
67            pos += base_length;
68            count -= base_length;
69        }
70        if count > 0 {
71            key = (key >> 16) ^ key;
72            self.decode(key, offset, buffer, pos, count)?;
73        }
74        Ok(())
75    }
76    fn decode(
77        &self,
78        key: u32,
79        offset: u64,
80        buffer: &mut [u8],
81        pos: usize,
82        count: usize,
83    ) -> Result<()>;
84}
85
86impl CxEncryption {
87    pub fn new(base: BaseSchema, schema: &CxSchema, filename: &str) -> Result<Arc<Self>> {
88        Ok(Arc::new(Self::new_inner(
89            base,
90            schema,
91            filename,
92            Box::new(CxProgramBuilder::default()),
93        )?))
94    }
95    fn new_inner(
96        base: BaseSchema,
97        schema: &CxSchema,
98        filename: &str,
99        program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
100    ) -> Result<Self> {
101        let control_block = if let Some(tpm_path) = &schema.tpm_file_name {
102            Self::read_tpm(tpm_path, filename)?
103        } else if let Some(control_block_name) = &schema.control_block_name {
104            CX_CB_TABLE
105                .get(control_block_name)
106                .ok_or_else(|| {
107                    anyhow::anyhow!(
108                        "Control block not found in cx_cb.pck: {}",
109                        control_block_name
110                    )
111                })?
112                .clone()
113        } else {
114            return Err(anyhow::anyhow!(
115                "TPM file name or control block is required in schema"
116            ));
117        };
118        Self::new_inner2(base, schema, program_builder, control_block)
119    }
120
121    fn new_inner2(
122        base: BaseSchema,
123        schema: &CxSchema,
124        program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
125        control_block: Vec<u32>,
126    ) -> Result<Self> {
127        if schema.prolog_order.len() != 3 {
128            return Err(anyhow::anyhow!("Prolog order must have 3 elements"));
129        }
130        if schema.odd_branch_order.len() != 6 {
131            return Err(anyhow::anyhow!("Odd branch order must have 6 elements"));
132        }
133        if schema.even_branch_order.len() != 8 {
134            return Err(anyhow::anyhow!("Even branch order must have 8 elements"));
135        }
136        let control_block = Arc::new(control_block);
137        let programs = Vec::with_capacity(0x80);
138        let mut obj = Self {
139            base,
140            mask: schema.mask,
141            offset: schema.offset,
142            prolog_order: schema.prolog_order.bytes.clone(),
143            odd_branch_order: schema.odd_branch_order.bytes.clone(),
144            even_branch_order: schema.even_branch_order.bytes.clone(),
145            control_block: control_block,
146            programs,
147            program_builder,
148        };
149        for seed in 0..0x80 {
150            obj.programs.push(obj.generate_program(seed)?);
151        }
152        Ok(obj)
153    }
154
155    fn new_program(&self, seed: u32) -> Box<dyn ICxProgram + Send + Sync> {
156        self.program_builder
157            .build(seed, Arc::downgrade(&self.control_block))
158    }
159
160    fn generate_program(&self, seed: u32) -> Result<Box<dyn ICxProgram + Send + Sync>> {
161        let mut program = self.new_program(seed);
162        for stage in (1..=5).rev() {
163            if self.emit_code(&mut program, stage) {
164                return Ok(program);
165            }
166            program.clear();
167        }
168        Err(anyhow::anyhow!("Overly large CxEncryption bytecode"))
169    }
170
171    fn read_tpm(tpm_path: &str, filename: &str) -> Result<Vec<u32>> {
172        let pfile = Self::get_tpm_path(tpm_path, filename)?;
173        let tpm = std::fs::read(&pfile)?;
174        let mut begin = 0;
175        let end = (tpm.len() - 0x1000) & !0x3;
176        while begin < end {
177            if &tpm[begin..begin + S_CTL_BLOCK_SIGNATURE.len()] == S_CTL_BLOCK_SIGNATURE {
178                let mut control_block = Vec::with_capacity(0x400);
179                let mut reader = MemReaderRef::new(&tpm[begin..]);
180                for _ in 0..0x400 {
181                    control_block.push(!reader.read_u32()?);
182                }
183                return Ok(control_block);
184            }
185            begin += 4;
186        }
187        Err(anyhow::anyhow!(
188            "Control block signature not found in TPM file: {}",
189            pfile.display()
190        ))
191    }
192
193    fn get_tpm_path(tpm_path: &str, filename: &str) -> Result<PathBuf> {
194        let pb = PathBuf::from(filename);
195        let pdir = pb
196            .parent()
197            .ok_or_else(|| anyhow::anyhow!("Invalid TPM path"))?;
198        let pfile = pdir.join(tpm_path);
199        if pfile.is_file() {
200            return Ok(pfile);
201        }
202        let pfile = pdir.join("..").join(tpm_path);
203        if pfile.is_file() {
204            return Ok(pfile);
205        }
206        Err(anyhow::anyhow!("TPM file not found: {}", tpm_path))
207    }
208
209    fn emit_code(&self, program: &mut Box<dyn ICxProgram + Send + Sync>, stage: i32) -> bool {
210        program.emit_nop(5)
211            && program.emit(MovEdiArg, 4)
212            && self.emit_body(program, stage)
213            && program.emit_nop(5)
214            && program.emit(Retn, 1)
215    }
216
217    fn emit_body(&self, program: &mut Box<dyn ICxProgram + Send + Sync>, stage: i32) -> bool {
218        if stage == 1 {
219            return self.emit_prolog(program);
220        }
221        if !program.emit(PushEbx, 1) {
222            return false;
223        }
224        if (program.get_random() & 1) != 0 {
225            if !self.emit_body(program, stage - 1) {
226                return false;
227            }
228        } else {
229            if !self.emit_body2(program, stage - 1) {
230                return false;
231            }
232        }
233        if !program.emit(MovEbxEax, 2) {
234            return false;
235        }
236        if (program.get_random() & 1) != 0 {
237            if !self.emit_body(program, stage - 1) {
238                return false;
239            }
240        } else {
241            if !self.emit_body2(program, stage - 1) {
242                return false;
243            }
244        }
245        self.emit_odd_branch(program) && program.emit(PopEbx, 1)
246    }
247
248    fn emit_body2(&self, program: &mut Box<dyn ICxProgram + Send + Sync>, stage: i32) -> bool {
249        if stage == 1 {
250            return self.emit_prolog(program);
251        }
252        let r = if (program.get_random() & 1) != 0 {
253            self.emit_body(program, stage - 1)
254        } else {
255            self.emit_body2(program, stage - 1)
256        };
257        r && self.emit_even_branch(program)
258    }
259    fn emit_prolog(&self, program: &mut Box<dyn ICxProgram + Send + Sync>) -> bool {
260        match self.prolog_order[(program.get_random() % 3) as usize] {
261            2 => {
262                program.emit_nop(5)
263                    && program.emit(MovEaxImmed, 2)
264                    && {
265                        let random = program.get_random() & 0x3ff;
266                        program.emit_u32(random)
267                    }
268                    && program.emit(MovEaxIndirect, 0)
269            }
270            1 => program.emit(MovEaxEdi, 2),
271            0 => program.emit(MovEaxImmed, 1) && program.emit_random(),
272            _ => true,
273        }
274    }
275
276    fn emit_even_branch(&self, program: &mut Box<dyn ICxProgram + Send + Sync>) -> bool {
277        match self.even_branch_order[(program.get_random() & 7) as usize] {
278            0 => program.emit(NotEax, 2),
279            1 => program.emit(DecEax, 1),
280            2 => program.emit(NegEax, 2),
281            3 => program.emit(IncEax, 1),
282            4 => {
283                program.emit_nop(5)
284                    && program.emit(AndEaxImmed, 1)
285                    && program.emit_u32(0x3ff)
286                    && program.emit(MovEaxIndirect, 3)
287            }
288            5 => {
289                program.emit(PushEbx, 1)
290                    && program.emit(MovEbxEax, 2)
291                    && program.emit(AndEbxImmed, 2)
292                    && program.emit_u32(0xaaaaaaaa)
293                    && program.emit(AndEaxImmed, 1)
294                    && program.emit_u32(0x55555555)
295                    && program.emit(ShrEbx1, 2)
296                    && program.emit(ShlEax1, 2)
297                    && program.emit(OrEaxEbx, 2)
298                    && program.emit(PopEbx, 1)
299            }
300            6 => program.emit(XorEaxImmed, 1) && program.emit_random(),
301            7 => {
302                let mut r = if (program.get_random() & 1) != 0 {
303                    program.emit(AddEaxImmed, 1)
304                } else {
305                    program.emit(SubEaxImmed, 1)
306                };
307                r = r && program.emit_random();
308                r
309            }
310            _ => true,
311        }
312    }
313
314    fn emit_odd_branch(&self, program: &mut Box<dyn ICxProgram + Send + Sync>) -> bool {
315        match self.odd_branch_order[(program.get_random() % 6) as usize] {
316            0 => {
317                program.emit(PushEcx, 1)
318                    && program.emit(MovEcxEbx, 2)
319                    && program.emit(AndEcx0F, 3)
320                    && program.emit(ShrEaxCl, 2)
321                    && program.emit(PopEcx, 1)
322            }
323            1 => {
324                program.emit(PushEcx, 1)
325                    && program.emit(MovEcxEbx, 2)
326                    && program.emit(AndEcx0F, 3)
327                    && program.emit(ShlEaxCl, 2)
328                    && program.emit(PopEcx, 1)
329            }
330            2 => program.emit(AddEaxEbx, 2),
331            3 => program.emit(NegEax, 2) && program.emit(AddEaxEbx, 2),
332            4 => program.emit(ImulEaxEbx, 3),
333            5 => program.emit(SubEaxEbx, 2),
334            _ => true,
335        }
336    }
337
338    fn execute_xcode(&self, mut hash: u32) -> Result<(u32, u32)> {
339        let seed = hash & 0x7f;
340        hash >>= 7;
341        let program = &self.programs[seed as usize];
342        let ret1 = program.execute(hash)?;
343        let ret2 = program.execute(!hash)?;
344        Ok((ret1, ret2))
345    }
346}
347
348impl AsRef<BaseSchema> for CxEncryption {
349    fn as_ref(&self) -> &BaseSchema {
350        &self.base
351    }
352}
353
354impl ICxEncryption for CxEncryption {
355    fn get_base_offset(&self, hash: u32) -> u32 {
356        (hash & self.mask).wrapping_add(self.offset)
357    }
358
359    fn decode(
360        &self,
361        key: u32,
362        offset: u64,
363        buffer: &mut [u8],
364        pos: usize,
365        count: usize,
366    ) -> Result<()> {
367        let ret = self.execute_xcode(key)?;
368        let key1 = ret.1 >> 16;
369        let mut key2 = ret.1 & 0xffff;
370        let mut key3 = (ret.0 & 0xFF) as u8;
371        if key1 == key2 {
372            key2 = key2.wrapping_add(1);
373        }
374        if key3 == 0 {
375            key3 = 1;
376        }
377        if (key2 as u64) >= offset && (key2 as u64) < offset + (count as u64) {
378            buffer[pos + key2 as usize - offset as usize] ^= ((ret.0 >> 16) & 0xFF) as u8;
379        }
380        if (key1 as u64) >= offset && (key1 as u64) < offset + (count as u64) {
381            buffer[pos + key1 as usize - offset as usize] ^= ((ret.0 >> 8) & 0xFF) as u8;
382        }
383        for i in 0..count {
384            buffer[pos + i] ^= key3;
385        }
386        Ok(())
387    }
388}
389
390macro_rules! icx_enc_arc_impl {
391    ($t:ident) => {
392        impl ICxEncryption for Arc<$t> {
393            fn get_base_offset(&self, hash: u32) -> u32 {
394                self.as_ref().get_base_offset(hash)
395            }
396            fn inner_decrypt(
397                &self,
398                key: u32,
399                offset: u64,
400                buffer: &mut [u8],
401                pos: usize,
402                count: usize,
403            ) -> Result<()> {
404                self.as_ref().inner_decrypt(key, offset, buffer, pos, count)
405            }
406            fn decode(
407                &self,
408                key: u32,
409                offset: u64,
410                buffer: &mut [u8],
411                pos: usize,
412                count: usize,
413            ) -> Result<()> {
414                self.as_ref().decode(key, offset, buffer, pos, count)
415            }
416        }
417    };
418}
419
420macro_rules! icx_enc_impl {
421    ($t:ident) => {
422        impl ICxEncryption for $t {
423            fn get_base_offset(&self, hash: u32) -> u32 {
424                self.base.get_base_offset(hash)
425            }
426            fn inner_decrypt(
427                &self,
428                key: u32,
429                offset: u64,
430                buffer: &mut [u8],
431                pos: usize,
432                count: usize,
433            ) -> Result<()> {
434                self.base.inner_decrypt(key, offset, buffer, pos, count)
435            }
436            fn decode(
437                &self,
438                key: u32,
439                offset: u64,
440                buffer: &mut [u8],
441                pos: usize,
442                count: usize,
443            ) -> Result<()> {
444                self.base.decode(key, offset, buffer, pos, count)
445            }
446        }
447    };
448}
449
450icx_enc_arc_impl!(CxEncryption);
451
452impl Crypt for Arc<CxEncryption> {
453    base_schema_impl!();
454    fn decrypt_supported(&self) -> bool {
455        true
456    }
457    fn decrypt_seek_supported(&self) -> bool {
458        true
459    }
460    fn decrypt<'a>(
461        &self,
462        entry: &Xp3Entry,
463        cur_seg: &Segment,
464        stream: Box<dyn Read + Send + Sync + 'a>,
465    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
466        let key = (
467            entry.file_hash,
468            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
469        );
470        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
471    }
472    fn decrypt_with_seek<'a>(
473        &self,
474        entry: &Xp3Entry,
475        cur_seg: &Segment,
476        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
477    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
478        let key = (
479            entry.file_hash,
480            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
481        );
482        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
483    }
484}
485
486const CX_PROGRAM_SIZE: usize = 0x80;
487
488#[derive(Debug)]
489struct CxProgram {
490    code: Vec<u32>,
491    control_block: Weak<Vec<u32>>,
492    length: usize,
493    seed: u32,
494}
495
496#[repr(u32)]
497#[derive(Debug, Clone, Copy, PartialEq, Eq, int_enum::IntEnum)]
498enum CxByteCode {
499    Nop,
500    Retn,
501    MovEdiArg,
502    PushEbx,
503    PopEbx,
504    PushEcx,
505    PopEcx,
506    MovEaxEbx,
507    MovEbxEax,
508    MovEcxEbx,
509    MovEaxControlBlock,
510    MovEaxEdi,
511    MovEaxIndirect,
512    AddEaxEbx,
513    SubEaxEbx,
514    ImulEaxEbx,
515    AndEcx0F,
516    ShrEbx1,
517    ShlEax1,
518    ShrEaxCl,
519    ShlEaxCl,
520    OrEaxEbx,
521    NotEax,
522    NegEax,
523    DecEax,
524    IncEax,
525    Immed = 0x100,
526    MovEaxImmed,
527    AndEbxImmed,
528    AndEaxImmed,
529    XorEaxImmed,
530    AddEaxImmed,
531    SubEaxImmed,
532}
533
534use CxByteCode::*;
535
536#[derive(Debug, Default)]
537struct Context {
538    eax: u32,
539    ebx: u32,
540    ecx: u32,
541    edi: u32,
542    stack: Vec<u32>,
543}
544
545#[derive(Debug)]
546struct CxProgramBuilder {}
547
548impl Default for CxProgramBuilder {
549    fn default() -> Self {
550        Self {}
551    }
552}
553
554trait ICxProgramBuilder: std::fmt::Debug {
555    fn build(&self, seed: u32, control_blocks: Weak<Vec<u32>>)
556    -> Box<dyn ICxProgram + Send + Sync>;
557}
558
559impl ICxProgramBuilder for CxProgramBuilder {
560    fn build(
561        &self,
562        seed: u32,
563        control_blocks: Weak<Vec<u32>>,
564    ) -> Box<dyn ICxProgram + Send + Sync> {
565        Box::new(CxProgram {
566            code: Vec::with_capacity(CX_PROGRAM_SIZE),
567            control_block: control_blocks,
568            length: 0,
569            seed,
570        })
571    }
572}
573
574trait ICxProgram: std::fmt::Debug {
575    fn execute(&self, hash: u32) -> Result<u32>;
576    fn clear(&mut self);
577    fn emit_nop(&mut self, count: usize) -> bool;
578    fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool;
579    fn emit_u32(&mut self, x: u32) -> bool;
580    fn emit_random(&mut self) -> bool {
581        let random = self.get_random();
582        self.emit_u32(random)
583    }
584    fn get_random(&mut self) -> u32;
585}
586
587impl ICxProgram for CxProgram {
588    fn execute(&self, hash: u32) -> Result<u32> {
589        let mut context = Context::default();
590        let mut iterator = self.code.iter();
591        let mut immed = 0u32;
592        while let Some(code) = iterator.next() {
593            let code = *code;
594            const IMMED: u32 = Immed as u32;
595            if IMMED == (code & IMMED) {
596                immed = *iterator.next().ok_or_else(|| {
597                    anyhow::anyhow!("Incomplete IMMED bytecode in CxEncryption program")
598                })?;
599            }
600            let bytecode = CxByteCode::try_from(code).map_err(|_| {
601                anyhow::anyhow!("Invalid bytecode in CxEncryption program: {:#X}", code)
602            })?;
603            match bytecode {
604                Nop => {}
605                Immed => {}
606                MovEdiArg => {
607                    context.edi = hash;
608                }
609                PushEbx => {
610                    context.stack.push(context.ebx);
611                }
612                PopEbx => {
613                    context.ebx = context.stack.pop().ok_or_else(|| {
614                        anyhow::anyhow!("Stack underflow in CxEncryption program")
615                    })?;
616                }
617                PushEcx => {
618                    context.stack.push(context.ecx);
619                }
620                PopEcx => {
621                    context.ecx = context.stack.pop().ok_or_else(|| {
622                        anyhow::anyhow!("Stack underflow in CxEncryption program")
623                    })?;
624                }
625                MovEbxEax => {
626                    context.ebx = context.eax;
627                }
628                MovEaxEdi => {
629                    context.eax = context.edi;
630                }
631                MovEcxEbx => {
632                    context.ecx = context.ebx;
633                }
634                MovEaxEbx => {
635                    context.eax = context.ebx;
636                }
637                AndEcx0F => {
638                    context.ecx &= 0x0F;
639                }
640                ShrEbx1 => {
641                    context.ebx >>= 1;
642                }
643                ShlEax1 => {
644                    context.eax <<= 1;
645                }
646                ShrEaxCl => {
647                    context.eax >>= context.ecx;
648                }
649                ShlEaxCl => {
650                    context.eax <<= context.ecx;
651                }
652                OrEaxEbx => {
653                    context.eax |= context.ebx;
654                }
655                NotEax => {
656                    context.eax = !context.eax;
657                }
658                NegEax => {
659                    context.eax = context.eax.wrapping_neg();
660                }
661                DecEax => {
662                    context.eax = context.eax.wrapping_sub(1);
663                }
664                IncEax => {
665                    context.eax = context.eax.wrapping_add(1);
666                }
667                AddEaxEbx => {
668                    context.eax = context.eax.wrapping_add(context.ebx);
669                }
670                SubEaxEbx => {
671                    context.eax = context.eax.wrapping_sub(context.ebx);
672                }
673                ImulEaxEbx => {
674                    context.eax = context.eax.wrapping_mul(context.ebx);
675                }
676                AddEaxImmed => {
677                    context.eax = context.eax.wrapping_add(immed);
678                }
679                SubEaxImmed => {
680                    context.eax = context.eax.wrapping_sub(immed);
681                }
682                AndEbxImmed => {
683                    context.ebx &= immed;
684                }
685                AndEaxImmed => {
686                    context.eax &= immed;
687                }
688                XorEaxImmed => {
689                    context.eax ^= immed;
690                }
691                MovEaxImmed => {
692                    context.eax = immed;
693                }
694                MovEaxIndirect => {
695                    let control_block = self
696                        .control_block
697                        .upgrade()
698                        .ok_or_else(|| anyhow::anyhow!("Control block has been dropped"))?;
699                    if context.eax as usize >= control_block.len() {
700                        return Err(anyhow::anyhow!(
701                            "Control block index out of bounds in CxEncryption program: {}",
702                            context.eax
703                        ));
704                    }
705                    context.eax = !control_block[context.eax as usize];
706                }
707                Retn => {
708                    if context.stack.len() != 0 {
709                        return Err(anyhow::anyhow!(
710                            "Stack not empty at RETN in CxEncryption program"
711                        ));
712                    }
713                    return Ok(context.eax);
714                }
715                _ => {
716                    return Err(anyhow::anyhow!(
717                        "Unsupported bytecode in CxEncryption program: {:?}",
718                        bytecode
719                    ));
720                }
721            }
722        }
723        Err(anyhow::anyhow!(
724            "CxEncryption program without RETN bytecode"
725        ))
726    }
727
728    fn clear(&mut self) {
729        self.length = 0;
730        self.code.clear();
731    }
732
733    fn emit_nop(&mut self, count: usize) -> bool {
734        if self.length + count > CX_PROGRAM_SIZE {
735            return false;
736        }
737        self.length += count;
738        return true;
739    }
740
741    fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
742        if self.length + length > CX_PROGRAM_SIZE {
743            return false;
744        }
745        self.code.push(bytecode as u32);
746        self.length += length;
747        return true;
748    }
749
750    fn emit_u32(&mut self, x: u32) -> bool {
751        if self.length + 4 > CX_PROGRAM_SIZE {
752            return false;
753        }
754        self.code.push(x);
755        self.length += 4;
756        return true;
757    }
758
759    fn get_random(&mut self) -> u32 {
760        let seed = self.seed;
761        self.seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
762        self.seed ^ (seed << 16) ^ (seed >> 16)
763    }
764}
765
766#[derive(msg_tool_macro::MyDebug)]
767struct CxEncryptionReader<'a, T> {
768    #[skip_fmt]
769    inner: T,
770    seg_start: u64,
771    seg_size: u64,
772    pos: u64,
773    key: (u32, Box<dyn ICxEncryption + Send + Sync + 'a>),
774}
775
776impl<'a, T: Read> CxEncryptionReader<'a, T> {
777    pub fn new(
778        inner: T,
779        seg: &Segment,
780        key: (u32, Box<dyn ICxEncryption + Send + Sync + 'a>),
781    ) -> Self {
782        Self {
783            inner,
784            seg_start: seg.offset_in_file,
785            seg_size: seg.original_size,
786            pos: 0,
787            key,
788        }
789    }
790}
791
792impl<'a, T: Read + Seek> Seek for CxEncryptionReader<'a, T> {
793    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
794        let new_pos: i64 = match pos {
795            SeekFrom::Start(offset) => offset as i64,
796            SeekFrom::End(offset) => self.seg_size as i64 + offset,
797            SeekFrom::Current(offset) => self.pos as i64 + offset,
798        };
799        let offset = new_pos - self.pos as i64;
800        if offset != 0 {
801            self.inner.seek(SeekFrom::Current(offset))?;
802            self.pos = new_pos as u64;
803        }
804        Ok(self.pos)
805    }
806}
807
808impl<'a, R: Read> Read for CxEncryptionReader<'a, R> {
809    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
810        let offset = self.seg_start + self.pos;
811        let count = self.inner.read(buf)?;
812        if count == 0 {
813            return Ok(0);
814        }
815        let key = self.key.0;
816        let cx = &self.key.1;
817        if let Err(e) = cx.inner_decrypt(key, offset, buf, 0, count) {
818            return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
819        }
820        self.pos += count as u64;
821        Ok(count)
822    }
823}
824
825#[derive(Debug)]
826pub struct SenrenCxCrypt {
827    base: CxEncryption,
828    names_section_id: String,
829}
830
831impl AsRef<BaseSchema> for SenrenCxCrypt {
832    fn as_ref(&self) -> &BaseSchema {
833        self.base.as_ref()
834    }
835}
836
837impl SenrenCxCrypt {
838    pub fn new(
839        base: BaseSchema,
840        schema: &CxSchema,
841        filename: &str,
842        names_section_id: String,
843    ) -> Result<Arc<Self>> {
844        Ok(Arc::new(Self::new_inner(
845            base,
846            schema,
847            filename,
848            Box::new(CxProgramBuilder::default()),
849            names_section_id,
850        )?))
851    }
852    fn new_inner(
853        base: BaseSchema,
854        schema: &CxSchema,
855        filename: &str,
856        program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
857        names_section_id: String,
858    ) -> Result<Self> {
859        let cx = CxEncryption::new_inner(base, schema, filename, program_builder)?;
860        Ok(Self {
861            base: cx,
862            names_section_id,
863        })
864    }
865
866    fn read_yuzu_names<'a>(
867        reader: Box<dyn ReadDebug + 'a>,
868        unpacked_size: u32,
869    ) -> Result<(HashMap<u32, String>, HashMap<String, String>)> {
870        let mut decoded = MemWriter::with_capacity(unpacked_size as usize);
871        {
872            let mut decoder = flate2::read::ZlibDecoder::new(reader);
873            std::io::copy(&mut decoder, &mut decoded)?;
874        }
875        let decoded = decoded.into_inner();
876        let mut reader = MemReader::new(decoded);
877        let mut hash_map = HashMap::new();
878        let mut md5_map = HashMap::new();
879        let mut dir_offset = 0u64;
880        while !reader.is_eof() {
881            let _entry_sign = reader.read_u32()?;
882            let mut entry_size = reader.read_u64()?;
883            dir_offset += 12 + entry_size;
884            let hash = reader.read_u32()?;
885            let name_len = reader.read_u16()?;
886            entry_size -= 6;
887            if (name_len as u64) * 2 <= entry_size {
888                let name = reader.read_exact_vec((name_len) as usize * 2)?;
889                let name = decode_to_string(Encoding::Utf16LE, &name, true)?;
890                if !hash_map.contains_key(&hash) {
891                    hash_map.insert(hash, name.clone());
892                }
893                let encoded = encode_string(Encoding::Utf16LE, &name.to_ascii_lowercase(), true)?;
894                let md5 = format!("{:x}", md5::compute(encoded));
895                md5_map.insert(md5, name);
896            }
897            reader.pos = dir_offset as usize;
898            md5_map.insert("$".into(), "startup.tjs".into());
899        }
900        Ok((hash_map, md5_map))
901    }
902}
903
904fn read_yuzu_names<'a, T>(
905    archive: &mut Xp3Archive<'a>,
906    names_section_id: &str,
907    convert: T,
908) -> Result<()>
909where
910    T: FnOnce(
911        Box<dyn ReadDebug + 'a>,
912        u32,
913    ) -> Result<(HashMap<u32, String>, HashMap<String, String>)>,
914{
915    if let Some(section) = archive.extras.iter().find(|s| s.tag == names_section_id) {
916        let mut sreader = MemReaderRef::new(&section.data);
917        let offset = sreader.read_u64()? + archive.base_offset;
918        let unpacked_size = sreader.read_u32()?;
919        let packed_size = sreader.read_u32()?;
920        let index_stream =
921            MutexWrapper::new(archive.inner.clone(), offset).take(packed_size as u64);
922        let (hash_map, md5_map) = convert(Box::new(index_stream), unpacked_size)?;
923        for entry in archive.entries.iter_mut() {
924            if let Some(name) = hash_map.get(&entry.file_hash) {
925                entry.name = name.clone();
926            } else if let Some(name) = md5_map.get(&entry.name) {
927                entry.name = name.clone();
928            }
929        }
930    }
931    archive.extras.retain(|s| s.tag != names_section_id);
932    Ok(())
933}
934
935icx_enc_impl!(SenrenCxCrypt);
936icx_enc_arc_impl!(SenrenCxCrypt);
937
938impl Crypt for Arc<SenrenCxCrypt> {
939    base_schema_impl!();
940    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
941        default_init_crypt(archive)?;
942        read_yuzu_names(
943            archive,
944            &self.names_section_id,
945            SenrenCxCrypt::read_yuzu_names,
946        )
947    }
948    fn decrypt_supported(&self) -> bool {
949        true
950    }
951    fn decrypt_seek_supported(&self) -> bool {
952        true
953    }
954    fn decrypt<'a>(
955        &self,
956        entry: &Xp3Entry,
957        cur_seg: &Segment,
958        stream: Box<dyn Read + Send + Sync + 'a>,
959    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
960        let key = (
961            entry.file_hash,
962            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
963        );
964        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
965    }
966    fn decrypt_with_seek<'a>(
967        &self,
968        entry: &Xp3Entry,
969        cur_seg: &Segment,
970        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
971    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
972        let key = (
973            entry.file_hash,
974            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
975        );
976        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
977    }
978}
979
980#[derive(Debug)]
981struct CxProgramNana {
982    base: CxProgram,
983    random_seed: u32,
984}
985
986impl CxProgramNana {
987    fn new(seed: u32, control_blocks: Weak<Vec<u32>>, random_seed: u32) -> Self {
988        Self {
989            base: CxProgram {
990                code: Vec::with_capacity(CX_PROGRAM_SIZE),
991                control_block: control_blocks,
992                length: 0,
993                seed,
994            },
995            random_seed,
996        }
997    }
998}
999
1000impl ICxProgram for CxProgramNana {
1001    fn execute(&self, hash: u32) -> Result<u32> {
1002        self.base.execute(hash)
1003    }
1004    fn clear(&mut self) {
1005        self.base.clear();
1006    }
1007    fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
1008        self.base.emit(bytecode, length)
1009    }
1010    fn emit_nop(&mut self, count: usize) -> bool {
1011        self.base.emit_nop(count)
1012    }
1013    fn emit_u32(&mut self, x: u32) -> bool {
1014        self.base.emit_u32(x)
1015    }
1016    fn get_random(&mut self) -> u32 {
1017        let mut s = self.base.seed ^ (self.base.seed << 17);
1018        s ^= (s << 18) | (s >> 15);
1019        self.base.seed = !s;
1020        let mut r = self.random_seed ^ (self.random_seed << 13);
1021        r ^= r >> 17;
1022        self.random_seed = r ^ (r << 5);
1023        self.base.seed ^ self.random_seed
1024    }
1025}
1026
1027#[derive(Debug)]
1028struct CxProgramNanaBuilder {
1029    random_seed: u32,
1030}
1031
1032impl CxProgramNanaBuilder {
1033    fn new(random_seed: u32) -> Self {
1034        Self { random_seed }
1035    }
1036}
1037
1038impl ICxProgramBuilder for CxProgramNanaBuilder {
1039    fn build(
1040        &self,
1041        seed: u32,
1042        control_blocks: Weak<Vec<u32>>,
1043    ) -> Box<dyn ICxProgram + Send + Sync> {
1044        Box::new(CxProgramNana::new(seed, control_blocks, self.random_seed))
1045    }
1046}
1047
1048#[derive(Debug)]
1049pub struct CabbageCxCrypt {
1050    base: SenrenCxCrypt,
1051}
1052
1053impl AsRef<BaseSchema> for CabbageCxCrypt {
1054    fn as_ref(&self) -> &BaseSchema {
1055        self.base.as_ref()
1056    }
1057}
1058
1059impl CabbageCxCrypt {
1060    pub fn new(
1061        base: BaseSchema,
1062        schema: &CxSchema,
1063        filename: &str,
1064        names_section_id: String,
1065        random_seed: u32,
1066    ) -> Result<Arc<Self>> {
1067        Ok(Arc::new(Self {
1068            base: SenrenCxCrypt::new_inner(
1069                base,
1070                schema,
1071                filename,
1072                Box::new(CxProgramNanaBuilder::new(random_seed)),
1073                names_section_id,
1074            )?,
1075        }))
1076    }
1077}
1078
1079icx_enc_impl!(CabbageCxCrypt);
1080icx_enc_arc_impl!(CabbageCxCrypt);
1081
1082impl Crypt for Arc<CabbageCxCrypt> {
1083    base_schema_impl!();
1084    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
1085        default_init_crypt(archive)?;
1086        read_yuzu_names(
1087            archive,
1088            &self.base.names_section_id,
1089            SenrenCxCrypt::read_yuzu_names,
1090        )
1091    }
1092    fn decrypt_supported(&self) -> bool {
1093        true
1094    }
1095    fn decrypt_seek_supported(&self) -> bool {
1096        true
1097    }
1098    fn decrypt<'a>(
1099        &self,
1100        entry: &Xp3Entry,
1101        cur_seg: &Segment,
1102        stream: Box<dyn Read + Send + Sync + 'a>,
1103    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1104        let key = (
1105            entry.file_hash,
1106            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1107        );
1108        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1109    }
1110    fn decrypt_with_seek<'a>(
1111        &self,
1112        entry: &Xp3Entry,
1113        cur_seg: &Segment,
1114        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1115    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1116        let key = (
1117            entry.file_hash,
1118            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1119        );
1120        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1121    }
1122}
1123
1124#[derive(Debug)]
1125struct NanaDecryptor {
1126    state: [u32; 27],
1127    seed: u64,
1128}
1129
1130impl NanaDecryptor {
1131    fn new(key: &[u32], seed1: u32, seed2: u32) -> Self {
1132        let mut state = [0u32; 27];
1133        let seed = (seed2 as u64) << 32 | (seed1 as u64);
1134        let mut s = [0u32; 3];
1135        let mut k = key[0];
1136        s[0] = key[1];
1137        s[1] = key[2];
1138        s[2] = key[3];
1139        state[0] = k;
1140        let mut dst = 1;
1141        for i in 0..26usize {
1142            let src = i % 3;
1143            let m = s[src].rotate_right(8);
1144            let n = (i as u32) ^ k.wrapping_add(m);
1145            k = n ^ k.rotate_left(3);
1146            state[dst] = k;
1147            dst += 1;
1148            s[src] = n;
1149        }
1150        Self { state, seed }
1151    }
1152
1153    fn decrypt(&self, data: &mut [u8]) {
1154        let mut i = 0;
1155        let mut offset = 0;
1156        let mut length = data.len();
1157        while length > 0 {
1158            offset += 1;
1159            let mut key = self.transform_key(offset ^ self.seed);
1160            let count = std::cmp::min(length, 8);
1161            for _ in 0..count {
1162                data[i] ^= (key & 0xFF) as u8;
1163                key >>= 8;
1164                i += 1;
1165            }
1166            length -= count;
1167        }
1168    }
1169
1170    fn transform_key(&self, key: u64) -> u64 {
1171        let mut lo = (key & 0xFFFFFFFF) as u32;
1172        let mut hi = (key >> 32) as u32;
1173        for i in 0..27 {
1174            hi = hi.rotate_right(8);
1175            hi = hi.wrapping_add(lo);
1176            hi ^= self.state[i];
1177            lo = lo.rotate_left(3);
1178            lo ^= hi;
1179        }
1180        (hi as u64) << 32 | (lo as u64)
1181    }
1182}
1183
1184#[derive(Debug)]
1185pub struct NanaCxCrypt {
1186    base: SenrenCxCrypt,
1187    decryptor: NanaDecryptor,
1188}
1189
1190impl AsRef<BaseSchema> for NanaCxCrypt {
1191    fn as_ref(&self) -> &BaseSchema {
1192        self.base.as_ref()
1193    }
1194}
1195
1196impl NanaCxCrypt {
1197    pub fn new(
1198        base: BaseSchema,
1199        schema: &CxSchema,
1200        filename: &str,
1201        names_section_id: String,
1202        random_seed: u32,
1203        yuz_key: &[u32],
1204    ) -> Result<Arc<Self>> {
1205        if yuz_key.len() != 6 {
1206            return Err(anyhow::anyhow!(
1207                "Invalid Yuzu keys for NanaCxCrypt: expected 6, got {}",
1208                yuz_key.len()
1209            ));
1210        }
1211        let cx = SenrenCxCrypt::new_inner(
1212            base,
1213            schema,
1214            filename,
1215            Box::new(CxProgramNanaBuilder::new(random_seed)),
1216            names_section_id,
1217        )?;
1218        let decryptor = NanaDecryptor::new(yuz_key, yuz_key[4], yuz_key[5]);
1219        Ok(Arc::new(Self {
1220            base: cx,
1221            decryptor,
1222        }))
1223    }
1224
1225    fn read_yuzu_names<'a>(
1226        &self,
1227        mut reader: Box<dyn ReadDebug + 'a>,
1228        unpacked_size: u32,
1229    ) -> Result<(HashMap<u32, String>, HashMap<String, String>)> {
1230        let mut prefix = Vec::with_capacity(0x100);
1231        (&mut reader).take(0x100).read_to_end(&mut prefix)?;
1232        self.decryptor.decrypt(&mut prefix);
1233        let reader = Box::new(PrefixStream::new(prefix, reader));
1234        SenrenCxCrypt::read_yuzu_names(reader, unpacked_size)
1235    }
1236}
1237
1238icx_enc_impl!(NanaCxCrypt);
1239icx_enc_arc_impl!(NanaCxCrypt);
1240
1241impl Crypt for Arc<NanaCxCrypt> {
1242    base_schema_impl!();
1243    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
1244        default_init_crypt(archive)?;
1245        read_yuzu_names(
1246            archive,
1247            &self.base.names_section_id,
1248            |reader, unpacked_size| self.read_yuzu_names(reader, unpacked_size),
1249        )
1250    }
1251    fn decrypt_supported(&self) -> bool {
1252        true
1253    }
1254    fn decrypt_seek_supported(&self) -> bool {
1255        true
1256    }
1257    fn decrypt<'a>(
1258        &self,
1259        entry: &Xp3Entry,
1260        cur_seg: &Segment,
1261        stream: Box<dyn Read + Send + Sync + 'a>,
1262    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1263        let key = (
1264            entry.file_hash,
1265            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1266        );
1267        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1268    }
1269    fn decrypt_with_seek<'a>(
1270        &self,
1271        entry: &Xp3Entry,
1272        cur_seg: &Segment,
1273        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1274    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1275        let key = (
1276            entry.file_hash,
1277            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1278        );
1279        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1280    }
1281}
1282
1283#[derive(Debug)]
1284struct YuzDecryptor {
1285    state: [u8; 64],
1286}
1287
1288impl YuzDecryptor {
1289    fn new(key1: &[u32], key2: &[u32], seed1: u32, seed2: u32) -> Self {
1290        let mut state = [0u8; 64];
1291        for i in 0..4 {
1292            state[i * 4..i * 4 + 4].copy_from_slice(&key2[i].to_le_bytes());
1293        }
1294        for i in 0..8 {
1295            state[i * 4 + 16..i * 4 + 20].copy_from_slice(&key1[i].to_le_bytes());
1296        }
1297        let t: u32 = !0;
1298        state[48..52].copy_from_slice(&t.to_le_bytes());
1299        state[52..56].copy_from_slice(&t.to_le_bytes());
1300        state[56..60].copy_from_slice(&(!seed1).to_le_bytes());
1301        state[60..64].copy_from_slice(&(!seed2).to_le_bytes());
1302        Self { state }
1303    }
1304
1305    fn decrypt(&self, data: &mut [u8]) {
1306        let mut state1 = [0u8; 64];
1307        let mut state2 = [0u8; 64];
1308        let mut i = 0;
1309        let mut offset: u64 = 0;
1310        let mut length = data.len();
1311        while length > 0 {
1312            state1.copy_from_slice(&self.state);
1313            state1[48..56].copy_from_slice(&(!offset).to_le_bytes());
1314            offset += 1;
1315            Self::transform_state(&state1, &mut state2, 8);
1316            let count = length.min(0x40);
1317            for j in 0..count {
1318                data[i] ^= state2[j];
1319                i += 1;
1320            }
1321            length -= count;
1322        }
1323    }
1324
1325    fn transform_state(state: &[u8], target: &mut [u8], length: usize) {
1326        let mut tmp = [0u32; 16];
1327        for i in 0..16 {
1328            tmp[i] = !u32::from_le_bytes([
1329                state[i * 4],
1330                state[i * 4 + 1],
1331                state[i * 4 + 2],
1332                state[i * 4 + 3],
1333            ]);
1334        }
1335        if length > 0 {
1336            for _ in 0..((length - 1) >> 1) + 1 {
1337                let mut t1 = w!(tmp[4] + tmp[0]);
1338                let mut t2 = (t1 ^ tmp[12]).rotate_left(16);
1339                let mut t3 = w!(t2 + tmp[8]);
1340                let mut t4 = (tmp[4] ^ t3).rotate_left(12);
1341                let mut t5 = w!(t4 + t1);
1342                let mut t6 = (t5 ^ t2).rotate_left(8);
1343                tmp[12] = t6;
1344                w!(t6 += t3);
1345                tmp[4] = (t4 ^ t6).rotate_left(7);
1346                t4 = (w!(tmp[5] + tmp[1]) ^ tmp[13]).rotate_left(16);
1347                t3 = (tmp[5] ^ w!(t4 + tmp[9])).rotate_left(12);
1348                t2 = w!(t3 + tmp[5] + tmp[1]);
1349                tmp[13] = (t2 ^ t4).rotate_left(8);
1350                w!(tmp[9] += tmp[13] + t4);
1351                tmp[5] = (t3 ^ tmp[9]).rotate_left(7);
1352                t4 = (w!(tmp[6] + tmp[2]) ^ tmp[14]).rotate_left(16);
1353                w!(tmp[10] += t4);
1354                t1 = (tmp[6] ^ tmp[10]).rotate_left(12);
1355                t3 = w!(t1 + tmp[6] + tmp[2]);
1356                tmp[14] = (t3 ^ t4).rotate_left(8);
1357                tmp[6] = (t1 ^ w!(tmp[14] + tmp[10])).rotate_left(7);
1358                w!(tmp[10] += tmp[14]);
1359                t4 = w!(tmp[7] + tmp[3]) ^ tmp[15];
1360                w!(tmp[3] += tmp[7]);
1361                t4 = t4.rotate_left(16);
1362                w!(tmp[11] += t4);
1363                t1 = (tmp[7] ^ tmp[11]).rotate_left(12);
1364                t4 ^= w!(t1 + tmp[3]);
1365                w!(tmp[3] += t1);
1366                t4 = t4.rotate_left(8);
1367                w!(tmp[11] += t4);
1368                t1 = (t1 ^ tmp[11]).rotate_left(7);
1369                w!(t5 += tmp[5]);
1370                w!(t2 += tmp[6]);
1371                t4 = (t5 ^ t4).rotate_left(16);
1372                w!(tmp[10] += t4);
1373                tmp[5] = (tmp[5] ^ tmp[10]).rotate_left(12);
1374                tmp[0] = w!(tmp[5] + t5);
1375                t4 = (tmp[0] ^ t4).rotate_left(8);
1376                tmp[15] = t4;
1377                w!(tmp[10] += t4);
1378                tmp[5] = (tmp[5] ^ tmp[10]).rotate_left(7);
1379                tmp[12] = (tmp[12] ^ t2).rotate_left(16);
1380                w!(tmp[11] += tmp[12]);
1381                t4 = (tmp[11] ^ tmp[6]).rotate_left(12);
1382                tmp[1] = w!(t4 + t2);
1383                tmp[12] = (tmp[12] ^ tmp[1]).rotate_left(8);
1384                w!(tmp[11] += tmp[12]);
1385                tmp[6] = (t4 ^ tmp[11]).rotate_left(7);
1386                w!(t3 += t1);
1387                t4 = (tmp[13] ^ t3).rotate_left(16);
1388                t2 = w!(t4 + t6);
1389                t1 = (t2 ^ t1).rotate_left(12);
1390                tmp[2] = w!(t1 + t3);
1391                tmp[13] = (t4 ^ tmp[2]).rotate_left(8);
1392                tmp[8] = w!(tmp[13] + t2);
1393                tmp[7] = (tmp[8] ^ t1).rotate_left(7);
1394                t6 = (tmp[14] ^ w!(tmp[4] + tmp[3])).rotate_left(16);
1395                t1 = (tmp[4] ^ w!(t6 + tmp[9])).rotate_left(12);
1396                w!(tmp[3] += t1 + tmp[4]);
1397                t3 = (t6 ^ tmp[3]).rotate_left(8);
1398                w!(tmp[9] += t3 + t6);
1399                tmp[4] = (t1 ^ tmp[9]).rotate_left(7);
1400                tmp[14] = t3;
1401            }
1402        }
1403        let mut pos = 0;
1404        for i in 0..16 {
1405            let d =
1406                !u32::from_le_bytes([state[pos], state[pos + 1], state[pos + 2], state[pos + 3]]);
1407            let d = w!(tmp[i] + d);
1408            target[pos..pos + 4].copy_from_slice(&d.to_le_bytes());
1409            pos += 4;
1410        }
1411    }
1412}
1413
1414#[derive(Debug)]
1415pub struct RiddleCxCrypt {
1416    base: SenrenCxCrypt,
1417    decryptor: YuzDecryptor,
1418    key1: u32,
1419    key2: u32,
1420}
1421
1422impl AsRef<BaseSchema> for RiddleCxCrypt {
1423    fn as_ref(&self) -> &BaseSchema {
1424        self.base.as_ref()
1425    }
1426}
1427
1428impl RiddleCxCrypt {
1429    pub fn new(
1430        base: BaseSchema,
1431        schema: &CxSchema,
1432        filename: &str,
1433        names_section_id: String,
1434        random_seed: u32,
1435        yuz_key: &[u32],
1436        key1: u32,
1437        key2: u32,
1438    ) -> Result<Arc<Self>> {
1439        if yuz_key.len() != 6 {
1440            return Err(anyhow::anyhow!(
1441                "Invalid Yuzu keys for RiddleCxCrypt: expected 6, got {}",
1442                yuz_key.len()
1443            ));
1444        }
1445        let cx = SenrenCxCrypt::new_inner(
1446            base,
1447            schema,
1448            filename,
1449            Box::new(CxProgramNanaBuilder::new(random_seed)),
1450            names_section_id,
1451        )?;
1452        let control_block = cx.base.control_block.as_ref();
1453        let decryptor = YuzDecryptor::new(&control_block, yuz_key, yuz_key[4], yuz_key[5]);
1454        Ok(Arc::new(Self {
1455            base: cx,
1456            decryptor,
1457            key1,
1458            key2,
1459        }))
1460    }
1461
1462    fn get_key_from_hash(&self, key: u32) -> u64 {
1463        let lo = key ^ self.key2;
1464        let mut hi = (key << 13) ^ key;
1465        hi ^= hi >> 17;
1466        hi ^= (hi << 5) ^ self.key1;
1467        ((hi as u64) << 32) | (lo as u64)
1468    }
1469
1470    fn read_yuzu_names<'a>(
1471        &self,
1472        mut reader: Box<dyn ReadDebug + 'a>,
1473        unpacked_size: u32,
1474    ) -> Result<(HashMap<u32, String>, HashMap<String, String>)> {
1475        let mut prefix = Vec::with_capacity(0x100);
1476        (&mut reader).take(0x100).read_to_end(&mut prefix)?;
1477        self.decryptor.decrypt(&mut prefix);
1478        let reader = Box::new(PrefixStream::new(prefix, reader));
1479        SenrenCxCrypt::read_yuzu_names(reader, unpacked_size)
1480    }
1481}
1482
1483impl ICxEncryption for RiddleCxCrypt {
1484    fn get_base_offset(&self, hash: u32) -> u32 {
1485        self.base.get_base_offset(hash)
1486    }
1487    fn inner_decrypt(
1488        &self,
1489        key: u32,
1490        offset: u64,
1491        buffer: &mut [u8],
1492        pos: usize,
1493        count: usize,
1494    ) -> Result<()> {
1495        if offset < 8 && count > 0 {
1496            let mut key = self.get_key_from_hash(key);
1497            key >>= offset << 3;
1498            let first_chunk = count.min(8 - offset as usize);
1499            for i in 0..first_chunk {
1500                buffer[pos + i] ^= (key & 0xFF) as u8;
1501                key >>= 8;
1502            }
1503        }
1504        self.base.inner_decrypt(key, offset, buffer, pos, count)
1505    }
1506    fn decode(
1507        &self,
1508        key: u32,
1509        offset: u64,
1510        buffer: &mut [u8],
1511        pos: usize,
1512        count: usize,
1513    ) -> Result<()> {
1514        self.base.decode(key, offset, buffer, pos, count)
1515    }
1516}
1517icx_enc_arc_impl!(RiddleCxCrypt);
1518
1519impl Crypt for Arc<RiddleCxCrypt> {
1520    base_schema_impl!();
1521    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
1522        default_init_crypt(archive)?;
1523        read_yuzu_names(
1524            archive,
1525            &self.base.names_section_id,
1526            |reader, unpacked_size| self.read_yuzu_names(reader, unpacked_size),
1527        )
1528    }
1529    fn decrypt_supported(&self) -> bool {
1530        true
1531    }
1532    fn decrypt_seek_supported(&self) -> bool {
1533        true
1534    }
1535    fn decrypt<'a>(
1536        &self,
1537        entry: &Xp3Entry,
1538        cur_seg: &Segment,
1539        stream: Box<dyn Read + Send + Sync + 'a>,
1540    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1541        let key = (
1542            entry.file_hash,
1543            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1544        );
1545        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1546    }
1547    fn decrypt_with_seek<'a>(
1548        &self,
1549        entry: &Xp3Entry,
1550        cur_seg: &Segment,
1551        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1552    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1553        let key = (
1554            entry.file_hash,
1555            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1556        );
1557        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1558    }
1559}
1560
1561#[derive(Debug)]
1562pub struct HxCryptLite {
1563    base: CxEncryption,
1564    header_key: Option<Vec<u8>>,
1565    header_split_position: u64,
1566    file_crypt_flag: bool,
1567}
1568
1569impl HxCryptLite {
1570    pub fn new(
1571        base: BaseSchema,
1572        schema: &CxSchema,
1573        filename: &str,
1574        header_key: Option<Vec<u8>>,
1575        header_split_position: u64,
1576        file_crypt_flag: bool,
1577        random_type: i32,
1578    ) -> Result<Arc<Self>> {
1579        if let Some(key) = header_key.as_ref() {
1580            if key.len() < 8 {
1581                anyhow::bail!("header_key is too small.");
1582            }
1583        }
1584        Ok(Arc::new(Self {
1585            base: CxEncryption::new_inner(
1586                base,
1587                schema,
1588                filename,
1589                Box::new(HxProgramLiteBuilder::new(random_type)),
1590            )?,
1591            header_key,
1592            header_split_position,
1593            file_crypt_flag,
1594        }))
1595    }
1596}
1597
1598impl AsRef<BaseSchema> for HxCryptLite {
1599    fn as_ref(&self) -> &BaseSchema {
1600        self.base.as_ref()
1601    }
1602}
1603
1604#[derive(Debug)]
1605struct HxProgramLite {
1606    base: CxProgram,
1607    random_type: i32,
1608    random_block: [u32; 0x270],
1609    block_position: usize,
1610}
1611
1612impl HxProgramLite {
1613    pub fn new(seed: u32, control_block: Weak<Vec<u32>>, random_method: i32) -> Self {
1614        let block_position = 0x270;
1615        let mut block = [0; 0x270];
1616        block[0] = seed;
1617        for i in 1..0x270 {
1618            block[i] =
1619                ((block[i - 1] ^ (block[i - 1] >> 0x1E)) * 0x6C078965).wrapping_add(i as u32);
1620        }
1621        Self {
1622            base: CxProgram {
1623                code: Vec::with_capacity(CX_PROGRAM_SIZE),
1624                control_block,
1625                length: 0,
1626                seed,
1627            },
1628            random_type: random_method,
1629            random_block: block,
1630            block_position,
1631        }
1632    }
1633
1634    fn get_random_new(&mut self) -> u32 {
1635        if self.block_position == 0x270 {
1636            self.transform_block();
1637        }
1638        let s0 = self.random_block[self.block_position];
1639        let s1 = (s0 >> 11) ^ s0;
1640        let s2 = ((s1 & 0xFF3A58AD) << 7) ^ s1;
1641        let s3 = ((s2 & 0xFFFFDF8C) << 15) ^ s2;
1642        let s4 = (s3 >> 18) ^ s3;
1643        self.block_position += 1;
1644        s4
1645    }
1646
1647    fn transform_block(&mut self) {
1648        self.block_position = 0;
1649        let block = &mut self.random_block;
1650        // 0-0xE2
1651        for i in 0..0xE3 {
1652            let s0 = if (block[i + 1] & 1) != 0 {
1653                0x9908B0DFu32
1654            } else {
1655                0
1656            };
1657            let s1 = (((block[i] ^ block[i + 1]) & 0x7FFFFFFE) ^ block[i]) >> 1;
1658            let s2 = s0 ^ s1 ^ block[i + 0x18D];
1659            block[i] = s2;
1660        }
1661        // 0xE3-0x26E
1662        for i in 0..0x18C {
1663            let s0 = if (block[i + 1 + 0xE3] & 1) != 0 {
1664                0x9908B0DFu32
1665            } else {
1666                0
1667            };
1668            let s1 =
1669                (((block[i + 0xE3] ^ block[i + 1 + 0xE3]) & 0x7FFFFFFE) ^ block[i + 0xE3]) >> 1;
1670            let s2 = s0 ^ s1 ^ block[i];
1671            block[i + 0xE3] = s2;
1672        }
1673        // 0x26F
1674        let s0 = if (block[0] & 1) != 0 {
1675            0x9908B0DFu32
1676        } else {
1677            0
1678        };
1679        let s1 = (((block[0x26F] ^ block[0]) & 0x7FFFFFFE) ^ block[0x26F]) >> 1;
1680        let s2 = s0 ^ s1 ^ block[0x18C];
1681        block[0x26F] = s2;
1682    }
1683}
1684
1685impl ICxProgram for HxProgramLite {
1686    fn execute(&self, hash: u32) -> Result<u32> {
1687        self.base.execute(hash)
1688    }
1689    fn clear(&mut self) {
1690        self.base.clear();
1691    }
1692    fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
1693        self.base.emit(bytecode, length)
1694    }
1695    fn emit_nop(&mut self, count: usize) -> bool {
1696        self.base.emit_nop(count)
1697    }
1698    fn emit_u32(&mut self, x: u32) -> bool {
1699        self.base.emit_u32(x)
1700    }
1701    fn get_random(&mut self) -> u32 {
1702        if self.random_type == 0 {
1703            self.base.get_random()
1704        } else {
1705            self.get_random_new()
1706        }
1707    }
1708}
1709
1710#[derive(Debug)]
1711struct HxProgramLiteBuilder {
1712    random_method: i32,
1713}
1714
1715impl HxProgramLiteBuilder {
1716    pub fn new(random_method: i32) -> Self {
1717        Self { random_method }
1718    }
1719}
1720
1721impl ICxProgramBuilder for HxProgramLiteBuilder {
1722    fn build(
1723        &self,
1724        seed: u32,
1725        control_blocks: Weak<Vec<u32>>,
1726    ) -> Box<dyn ICxProgram + Send + Sync> {
1727        Box::new(HxProgramLite::new(seed, control_blocks, self.random_method))
1728    }
1729}
1730
1731struct HxFileDecryptor {
1732    split_pos1: u64,
1733    split_pos2: u64,
1734    key: u32,
1735    key1: u8,
1736    key2: u8,
1737}
1738
1739impl HxFileDecryptor {
1740    fn new(key: u64, file_key_flag: bool) -> Self {
1741        let key_ptr = key.to_le_bytes();
1742        let mut global_key = key_ptr[0] as u32;
1743        let mut key1 = key_ptr[1];
1744        let mut key2 = key_ptr[2];
1745        let split_pos1 = u16::from_le_bytes([key_ptr[6], key_ptr[7]]) as u64;
1746        let mut split_pos2 = u16::from_le_bytes([key_ptr[4], key_ptr[5]]) as u64;
1747        if split_pos1 == split_pos2 {
1748            split_pos2 += 1;
1749        }
1750        if global_key == 0 {
1751            global_key = 1;
1752        }
1753        global_key = global_key.wrapping_mul(0x01010101);
1754        if file_key_flag {
1755            key1 = 0;
1756            key2 = 0;
1757        }
1758        Self {
1759            split_pos1,
1760            split_pos2,
1761            key: global_key,
1762            key1,
1763            key2,
1764        }
1765    }
1766
1767    fn decrypt(&self, data: &mut [u8], offset: u64, pos: usize, count: usize) {
1768        if count == 0 {
1769            return;
1770        }
1771        let key = self.key.to_le_bytes();
1772        let mut key_pos = (offset & 3) as usize;
1773        for i in 0..count {
1774            data[pos + i] ^= key[key_pos];
1775            key_pos = (key_pos + 1) & 3;
1776        }
1777        let count = count as u64;
1778        if self.split_pos1 >= offset && self.split_pos1 < offset + count {
1779            data[(self.split_pos1 - offset) as usize + pos] ^= self.key1;
1780        }
1781        if self.split_pos2 >= offset && self.split_pos2 < offset + count {
1782            data[(self.split_pos2 - offset) as usize + pos] ^= self.key2;
1783        }
1784    }
1785}
1786
1787struct HxHeaderDecryptor {
1788    key: [u8; 8],
1789    pos: u64,
1790}
1791
1792impl HxHeaderDecryptor {
1793    fn new(hash: u32, key: &[u8], pos: u64) -> Self {
1794        let key_ptr = [
1795            u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
1796            u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
1797        ];
1798        let s0 = hash ^ key_ptr[1];
1799        let s1 = hash ^ (hash << 13);
1800        let s2 = s1 ^ (s1 >> 17);
1801        let s3 = s2 ^ (s2 << 5) ^ key_ptr[0];
1802        let key = ((s3 as u64) << 32) | (s0 as u64);
1803        Self {
1804            key: key.to_le_bytes(),
1805            pos,
1806        }
1807    }
1808
1809    fn decrypt(&self, data: &mut [u8], offset: u64, pos: usize, count: usize) {
1810        let mut start_pos = offset;
1811        if start_pos <= self.pos {
1812            start_pos = self.pos;
1813        }
1814        let mut end_pos = offset + count as u64;
1815        if end_pos >= self.pos + 8 {
1816            end_pos = self.pos + 8;
1817        }
1818        if start_pos >= end_pos {
1819            return;
1820        }
1821        let dlen = end_pos - start_pos;
1822        let key_start_index = start_pos - self.pos;
1823        let data_start_index = start_pos - offset + pos as u64;
1824        for i in 0..dlen {
1825            data[(data_start_index + i) as usize] ^= self.key[(key_start_index + i) as usize];
1826        }
1827    }
1828}
1829
1830impl ICxEncryption for HxCryptLite {
1831    fn get_base_offset(&self, _hash: u32) -> u32 {
1832        _hash
1833    }
1834    fn inner_decrypt(
1835        &self,
1836        hash: u32,
1837        offset: u64,
1838        buffer: &mut [u8],
1839        pos: usize,
1840        count: usize,
1841    ) -> Result<()> {
1842        if let Some(key) = self.header_key.as_ref() {
1843            let dec = HxHeaderDecryptor::new(hash, &key, self.header_split_position);
1844            dec.decrypt(buffer, offset, pos, count);
1845        }
1846        let ret1 = self.base.execute_xcode(hash)?;
1847        let ret2 = self.base.execute_xcode(hash ^ (hash >> 16))?;
1848        let key1 = ((ret1.1 as u64) << 32) | (ret1.0 as u64);
1849        let key2 = ((ret2.1 as u64) << 32) | (ret2.0 as u64);
1850        let split_pos = (self.base.offset + (hash & self.base.mask)) as u64;
1851        let dec1 = HxFileDecryptor::new(key1, self.file_crypt_flag);
1852        let dec2 = HxFileDecryptor::new(key2, self.file_crypt_flag);
1853        if split_pos > offset {
1854            if split_pos < offset + count as u64 {
1855                let blen1 = split_pos - offset;
1856                let blen2 = offset + count as u64 - split_pos;
1857                dec1.decrypt(buffer, offset, pos, blen1 as usize);
1858                dec2.decrypt(buffer, offset + blen1, pos + blen1 as usize, blen2 as usize);
1859            } else {
1860                dec1.decrypt(buffer, offset, pos, count);
1861            }
1862        } else {
1863            dec2.decrypt(buffer, offset, pos, count);
1864        }
1865        Ok(())
1866    }
1867    fn decode(
1868        &self,
1869        _key: u32,
1870        _offset: u64,
1871        _buffer: &mut [u8],
1872        _pos: usize,
1873        _count: usize,
1874    ) -> Result<()> {
1875        Ok(())
1876    }
1877}
1878
1879icx_enc_arc_impl!(HxCryptLite);
1880
1881impl Crypt for Arc<HxCryptLite> {
1882    base_schema_impl!();
1883    fn decrypt_supported(&self) -> bool {
1884        true
1885    }
1886    fn decrypt_seek_supported(&self) -> bool {
1887        true
1888    }
1889    fn decrypt<'a>(
1890        &self,
1891        entry: &Xp3Entry,
1892        cur_seg: &Segment,
1893        stream: Box<dyn Read + Send + Sync + 'a>,
1894    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1895        let key = (
1896            entry.file_hash,
1897            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1898        );
1899        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1900    }
1901    fn decrypt_with_seek<'a>(
1902        &self,
1903        entry: &Xp3Entry,
1904        cur_seg: &Segment,
1905        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1906    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1907        let key = (
1908            entry.file_hash,
1909            Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1910        );
1911        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1912    }
1913}
1914
1915#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1916struct FileHash([u8; 32]);
1917
1918impl std::fmt::Debug for FileHash {
1919    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1920        write!(f, "FileHash(")?;
1921        write!(f, "{}", hex::encode(self.0))?;
1922        write!(f, ")")
1923    }
1924}
1925
1926impl std::fmt::Display for FileHash {
1927    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1928        write!(f, "{}", hex::encode(self.0))
1929    }
1930}
1931
1932impl<'a> TryFrom<&'a [u8]> for FileHash {
1933    type Error = anyhow::Error;
1934    fn try_from(value: &'a [u8]) -> Result<Self> {
1935        Ok(Self(value.try_into()?))
1936    }
1937}
1938
1939impl<'a> TryFrom<&'a str> for FileHash {
1940    type Error = anyhow::Error;
1941    fn try_from(value: &'a str) -> Result<Self> {
1942        Self::try_from(hex::decode(value)?.as_slice())
1943    }
1944}
1945
1946impl<'de> Deserialize<'de> for FileHash {
1947    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1948    where
1949        D: Deserializer<'de>,
1950    {
1951        let s = String::deserialize(deserializer)?;
1952        let bytes = hex::decode(&s).map_err(de::Error::custom)?;
1953        let arr = bytes.try_into().map_err(|bytes: Vec<u8>| {
1954            de::Error::custom(format!(
1955                "FileHash length mismatch: expected 32 bytes, got {}",
1956                bytes.len()
1957            ))
1958        })?;
1959        Ok(FileHash(arr))
1960    }
1961}
1962
1963impl Serialize for FileHash {
1964    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1965    where
1966        S: Serializer,
1967    {
1968        serializer.serialize_str(&hex::encode(self.0))
1969    }
1970}
1971
1972impl FileHash {
1973    fn to_string(&self) -> String {
1974        hex::encode(&self.0)
1975    }
1976}
1977
1978#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1979struct PathHash(u64);
1980
1981impl std::fmt::Debug for PathHash {
1982    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1983        write!(f, "PathHash({:#x})", self.0)
1984    }
1985}
1986
1987impl std::fmt::Display for PathHash {
1988    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1989        write!(f, "{}", hex::encode(&self.0.to_be_bytes()))
1990    }
1991}
1992
1993impl<'de> Deserialize<'de> for PathHash {
1994    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1995    where
1996        D: Deserializer<'de>,
1997    {
1998        let s = String::deserialize(deserializer)?;
1999        let bytes = hex::decode(&s).map_err(de::Error::custom)?;
2000        let arr: [u8; 8] = bytes.try_into().map_err(|bytes: Vec<u8>| {
2001            de::Error::custom(format!(
2002                "PathHash length mismatch: expected 8 bytes, got {}",
2003                bytes.len()
2004            ))
2005        })?;
2006        Ok(PathHash(u64::from_be_bytes(arr)))
2007    }
2008}
2009
2010impl Serialize for PathHash {
2011    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2012    where
2013        S: Serializer,
2014    {
2015        serializer.serialize_str(&hex::encode(self.0.to_be_bytes()))
2016    }
2017}
2018
2019impl<'a> TryFrom<&'a [u8]> for PathHash {
2020    type Error = anyhow::Error;
2021    fn try_from(value: &'a [u8]) -> Result<Self> {
2022        let arr: [u8; 8] = value.try_into()?;
2023        Ok(PathHash(u64::from_be_bytes(arr)))
2024    }
2025}
2026
2027impl<'a> TryFrom<&'a str> for PathHash {
2028    type Error = anyhow::Error;
2029    fn try_from(value: &'a str) -> Result<Self> {
2030        Self::try_from(hex::decode(value)?.as_slice())
2031    }
2032}
2033
2034impl PathHash {
2035    fn to_string(&self) -> String {
2036        hex::encode(&self.0.to_be_bytes())
2037    }
2038}
2039
2040#[derive(Clone, Debug, Deserialize, Serialize)]
2041#[serde(rename_all = "camelCase")]
2042struct KeyData {
2043    boot_strap: String,
2044    warning: String,
2045    #[serde(with = "hex_vec")]
2046    params: Vec<u8>,
2047    archive_unique_key: String,
2048    #[serde(rename = "seed", with = "hex_vec_optional", default)]
2049    upper_key: Option<Vec<u8>>,
2050}
2051
2052mod hex_vec {
2053    use serde::{Deserialize, Deserializer, Serializer};
2054    pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
2055    where
2056        S: Serializer,
2057    {
2058        serializer.serialize_str(&hex::encode(bytes))
2059    }
2060    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
2061    where
2062        D: Deserializer<'de>,
2063    {
2064        let s = String::deserialize(deserializer)?;
2065        hex::decode(s).map_err(serde::de::Error::custom)
2066    }
2067}
2068
2069mod hex_vec_optional {
2070    use super::hex_vec;
2071    use serde::{Deserialize, Deserializer, Serializer};
2072    pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
2073    where
2074        S: Serializer,
2075    {
2076        match bytes {
2077            Some(b) => hex_vec::serialize(b, serializer),
2078            None => serializer.serialize_none(),
2079        }
2080    }
2081    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
2082    where
2083        D: Deserializer<'de>,
2084    {
2085        let s: Option<String> = Option::deserialize(deserializer)?;
2086        match s {
2087            Some(hex_str) => hex::decode(hex_str)
2088                .map(Some)
2089                .map_err(serde::de::Error::custom),
2090            None => Ok(None),
2091        }
2092    }
2093}
2094
2095#[derive(Clone, Debug, Deserialize, Serialize)]
2096pub struct KeyPackage {
2097    #[allow(unused)]
2098    description: String,
2099    key: KeyData,
2100    sku: String,
2101}
2102
2103#[derive(Clone, Deserialize, Serialize, msg_tool_macro::Default)]
2104#[serde(rename_all = "camelCase")]
2105struct CxdecDb {
2106    #[allow(unused)]
2107    #[default("xp3hnp".into())]
2108    file_hash_salt: String,
2109    /// xp3 filename -> path hash -> file hash -> file name
2110    file_list: HashMap<String, HashMap<PathHash, HashMap<FileHash, Option<String>>>>,
2111    #[serde(default)]
2112    key_packages: Vec<KeyPackage>,
2113    #[allow(unused)]
2114    #[default("xp3hnp".into())]
2115    path_hash_salt: String,
2116    path_mapping: HashMap<PathHash, Option<String>>,
2117    project_name: String,
2118}
2119
2120impl std::fmt::Debug for CxdecDb {
2121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2122        f.debug_struct("CxdecDb").finish_non_exhaustive()
2123    }
2124}
2125
2126#[derive(Debug)]
2127pub struct HxCrypt {
2128    base: CxEncryption,
2129    key1: IndexKey,
2130    key2: IndexKeys,
2131    filter_key: AtomicU64,
2132    file_mapping: Arc<HashMap<FileHash, String>>,
2133    path_mapping: Arc<HashMap<PathHash, String>>,
2134    info_map: Mutex<HashMap<String, HxEntry>>,
2135    file_hash: FileHashOption,
2136    path_hash: PathHashOption,
2137    dump_file_hash: Option<String>,
2138    filename: String,
2139}
2140
2141#[derive(Clone)]
2142pub struct IndexKey {
2143    key: [u8; 32],
2144    nonce: [u8; 16],
2145    filter_key: Option<u64>,
2146}
2147
2148impl std::fmt::Debug for IndexKey {
2149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2150        f.debug_struct("IndexKey")
2151            .field("key", &hex::encode(&self.key))
2152            .field("nonce", &hex::encode(&self.nonce))
2153            .field("filter_key", &self.filter_key)
2154            .finish()
2155    }
2156}
2157
2158#[derive(Deserialize)]
2159#[serde(rename_all = "PascalCase")]
2160struct IndexKeyTmp {
2161    key: String,
2162    nonce: String,
2163    #[serde(default)]
2164    filter_key: Option<u64>,
2165}
2166
2167impl<'de> Deserialize<'de> for IndexKey {
2168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2169    where
2170        D: Deserializer<'de>,
2171    {
2172        use base64::{Engine, engine::general_purpose::STANDARD};
2173        let s = IndexKeyTmp::deserialize(deserializer)?;
2174        let bytes = STANDARD.decode(&s.key).map_err(de::Error::custom)?;
2175        let key: [u8; 32] = bytes.try_into().map_err(|bytes: Vec<u8>| {
2176            de::Error::custom(format!(
2177                "Index key length mismatch: expected 32 bytes, got {}",
2178                bytes.len()
2179            ))
2180        })?;
2181        let hbytes = STANDARD.decode(&s.nonce).map_err(de::Error::custom)?;
2182        let nonce: [u8; 16] = hbytes.try_into().map_err(|bytes: Vec<u8>| {
2183            de::Error::custom(format!(
2184                "Index key nonce length mismatch: expected 16 bytes, got {}",
2185                bytes.len()
2186            ))
2187        })?;
2188        Ok(Self {
2189            key,
2190            nonce,
2191            filter_key: s.filter_key,
2192        })
2193    }
2194}
2195
2196#[derive(Clone, Debug)]
2197pub struct IndexKeys(pub Vec<IndexKey>);
2198
2199impl Deref for IndexKeys {
2200    type Target = Vec<IndexKey>;
2201    fn deref(&self) -> &Self::Target {
2202        &self.0
2203    }
2204}
2205
2206impl DerefMut for IndexKeys {
2207    fn deref_mut(&mut self) -> &mut Self::Target {
2208        &mut self.0
2209    }
2210}
2211
2212#[derive(Deserialize)]
2213#[serde(untagged)]
2214enum IndexKeysTmp {
2215    List(Vec<IndexKey>),
2216    Single(IndexKey),
2217}
2218
2219impl<'de> Deserialize<'de> for IndexKeys {
2220    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2221    where
2222        D: Deserializer<'de>,
2223    {
2224        let tmp = IndexKeysTmp::deserialize(deserializer)?;
2225        Ok(match tmp {
2226            IndexKeysTmp::List(list) => Self(list),
2227            IndexKeysTmp::Single(one) => Self(vec![one]),
2228        })
2229    }
2230}
2231
2232impl HxCrypt {
2233    pub fn new(
2234        base: BaseSchema,
2235        cx: &CxSchema,
2236        index_key1: &IndexKey,
2237        index_key2: &IndexKeys,
2238        filter_key: u64,
2239        random_type: i32,
2240        file_list_name: Option<&str>,
2241        file_list_path: Option<&str>,
2242        filename: &str,
2243        config: &ExtraConfig,
2244    ) -> Result<Self> {
2245        let p = std::path::Path::new(filename);
2246        let b = p
2247            .file_name()
2248            .ok_or_else(|| anyhow::anyhow!("Failed to get file name from path."))?;
2249        let s: &str = &b.to_string_lossy();
2250        let (file_map, mut path_map) = if let Some(path) = file_list_path {
2251            let data = std::fs::read(path)?;
2252            let data = decode_to_string(Encoding::Utf8, &data, true)?;
2253            Self::read_names(&data, s)?
2254        } else if let Some(name) = file_list_name {
2255            let flist = query_filename_list(name)?;
2256            Self::read_names(&flist, s)?
2257        } else {
2258            let pdir = p.parent().map(|s| s.to_owned()).unwrap_or_default();
2259            if let Some(k) = Self::try_default_name(&pdir.join("filelist.json"), s)? {
2260                k
2261            } else if let Some(k) = Self::try_default_name(&pdir.join("filelist.lst"), s)? {
2262                k
2263            } else {
2264                (HashMap::new(), HashMap::new())
2265            }
2266        };
2267        let default_path_hash = calculate_path_hash("", "xp3hnp");
2268        if !path_map.contains_key(&default_path_hash) {
2269            path_map.insert(default_path_hash, String::new());
2270        }
2271        Ok(Self {
2272            base: CxEncryption::new_inner(
2273                base,
2274                cx,
2275                filename,
2276                Box::new(HxProgramBuilder::new(random_type)),
2277            )?,
2278            key1: index_key1.clone(),
2279            key2: index_key2.clone(),
2280            filter_key: AtomicU64::new(filter_key),
2281            file_mapping: Arc::new(file_map),
2282            path_mapping: Arc::new(path_map),
2283            info_map: Mutex::new(HashMap::new()),
2284            file_hash: config.xp3_cxdec_file_hash,
2285            path_hash: config.xp3_cxdec_path_hash,
2286            dump_file_hash: config.xp3_dump_file_hash_list.clone(),
2287            filename: filename.to_owned(),
2288        })
2289    }
2290
2291    fn new_inner(
2292        base: BaseSchema,
2293        cx: &CxSchema,
2294        index_key1: IndexKey,
2295        index_key2: IndexKeys,
2296        filter_key: u64,
2297        random_type: i32,
2298        config: &ExtraConfig,
2299        file_mapping: Arc<HashMap<FileHash, String>>,
2300        path_mapping: Arc<HashMap<PathHash, String>>,
2301        control_block: Vec<u32>,
2302        filename: &str,
2303    ) -> Result<Self> {
2304        Ok(Self {
2305            base: CxEncryption::new_inner2(
2306                base,
2307                cx,
2308                Box::new(HxProgramBuilder::new(random_type)),
2309                control_block,
2310            )?,
2311            key1: index_key1,
2312            key2: index_key2,
2313            filter_key: AtomicU64::new(filter_key),
2314            file_mapping,
2315            path_mapping,
2316            info_map: Mutex::new(HashMap::new()),
2317            file_hash: config.xp3_cxdec_file_hash,
2318            path_hash: config.xp3_cxdec_path_hash,
2319            dump_file_hash: config.xp3_dump_file_hash_list.clone(),
2320            filename: filename.to_owned(),
2321        })
2322    }
2323
2324    fn try_default_name<P: AsRef<std::path::Path>>(
2325        s: &P,
2326        b: &str,
2327    ) -> Result<Option<(HashMap<FileHash, String>, HashMap<PathHash, String>)>> {
2328        let n = match get_ignorecase_path(s) {
2329            Ok(s) => s,
2330            Err(_) => return Ok(None),
2331        };
2332        if !n.exists() {
2333            return Ok(None);
2334        }
2335        let s = std::fs::read(&n)?;
2336        let data = decode_to_string(Encoding::Utf8, &s, true)?;
2337        let names = Self::read_names(&data, b)?;
2338        eprintln!(
2339            "Read {} file entries and {} directory entries from filelist {}.",
2340            names.0.len(),
2341            names.1.len(),
2342            n.display()
2343        );
2344        Ok(Some(names))
2345    }
2346
2347    fn read_names(
2348        s: &str,
2349        basename: &str,
2350    ) -> Result<(HashMap<FileHash, String>, HashMap<PathHash, String>)> {
2351        if let Ok(s) = serde_json::from_str::<CxdecDb>(&s) {
2352            let path_map: HashMap<_, _> = s
2353                .path_mapping
2354                .iter()
2355                .filter_map(|(k, v)| match v {
2356                    Some(v) => Some((k.clone(), v.clone())),
2357                    None => None,
2358                })
2359                .collect();
2360            let file_map: HashMap<_, _> = if let Some(s) = s.file_list.get(basename) {
2361                s.iter()
2362                    .map(|s| s.1)
2363                    .flatten()
2364                    .filter_map(|(k, v)| match v {
2365                        Some(v) => Some((k.clone(), v.clone())),
2366                        None => None,
2367                    })
2368                    .collect()
2369            } else {
2370                HashMap::new()
2371            };
2372            return Ok((file_map, path_map));
2373        }
2374        let mut file_map = HashMap::new();
2375        let mut path_map = HashMap::new();
2376        for line in s.lines() {
2377            let line = line.trim();
2378            if line.is_empty() {
2379                continue;
2380            }
2381            let mut iter = line.splitn(2, ':');
2382            let key = match iter.next() {
2383                Some(v) => v,
2384                None => continue,
2385            };
2386            let value = match iter.next() {
2387                Some(v) => v,
2388                None => continue,
2389            };
2390            if key.len() == 16 {
2391                let key = PathHash::try_from(key)?;
2392                path_map.insert(key, value.to_string());
2393            } else if key.len() == 64 {
2394                let key = FileHash::try_from(key)?;
2395                file_map.insert(key, value.to_string());
2396            }
2397        }
2398        Ok((file_map, path_map))
2399    }
2400
2401    fn create_chacha20_crypt(&self, flags: u16, key_index: usize) -> Result<ChaCha20Legacy> {
2402        use chacha20::{KeyIvInit, cipher::StreamCipherSeek};
2403        let key = match flags {
2404            0 => self
2405                .key2
2406                .get(key_index)
2407                .ok_or_else(|| anyhow::anyhow!("Index out of bound"))?,
2408            1 => &self.key1,
2409            _ => anyhow::bail!("Unknown hxv4 flags: {}", flags),
2410        };
2411        if let Some(filter_key) = &key.filter_key {
2412            self.filter_key.qsave(*filter_key);
2413        }
2414        let mut nonce = [0; 8];
2415        nonce.copy_from_slice(&key.nonce[..8]);
2416        let mut crypt = ChaCha20Legacy::new((&key.key).into(), (&nonce).into());
2417        crypt.try_seek(64)?;
2418        Ok(crypt)
2419    }
2420
2421    fn read_index_stream_internel<T: Read + Seek>(
2422        &self,
2423        stream: &mut T,
2424        flags: u16,
2425        key_index: usize,
2426    ) -> Result<MemReader> {
2427        use chacha20::cipher::StreamCipher;
2428        stream.rewind()?;
2429        let len = stream.stream_length()?;
2430        let mut crypt = self.create_chacha20_crypt(flags, key_index)?;
2431        let tlen = len as usize - 16;
2432        let mut buf = Vec::with_capacity(tlen);
2433        stream.seek(SeekFrom::Start(16))?;
2434        stream.read_to_end(&mut buf)?;
2435        crypt.try_apply_keystream(&mut buf)?;
2436        let mut stream = flate2::read::ZlibDecoder::new(MemReaderRef::new(&buf[4..]));
2437        let mut buf = Vec::new();
2438        stream.read_to_end(&mut buf)?;
2439        Ok(MemReader::new(buf))
2440    }
2441
2442    fn read_index_stream<T: Read + Seek>(&self, mut stream: T, flags: u16) -> Result<MemReader> {
2443        if flags != 0 {
2444            self.read_index_stream_internel(&mut stream, flags, 0)
2445        } else if self.key2.len() == 1 {
2446            self.read_index_stream_internel(&mut stream, flags, 0)
2447        } else {
2448            for i in 0..self.key2.len() {
2449                if let Ok(reader) = self.read_index_stream_internel(&mut stream, flags, i) {
2450                    return Ok(reader);
2451                }
2452            }
2453            anyhow::bail!("All index key decrypt failed.")
2454        }
2455    }
2456
2457    fn read_manifest(mainfest_dir: &str) -> Result<CxdecDb> {
2458        Ok(serde_json::from_reader(std::fs::File::open(mainfest_dir)?)?)
2459    }
2460
2461    fn read_index<T: Read + Seek>(&self, stream: T, flags: u16) -> Result<()> {
2462        let mut reader = self.read_index_stream(stream, flags)?;
2463        let root_obj = TjsValue::unpack(&mut reader, true, Encoding::Utf16LE, &None)?;
2464        if !root_obj.is_array() {
2465            anyhow::bail!("Index object is not an array.");
2466        }
2467        let mut info_map = self.info_map.lock_blocking();
2468        info_map.clear();
2469        let set = create_garbage_filename_set("xp3hnp");
2470        let basename: &str = &std::path::Path::new(&self.filename)
2471            .file_name()
2472            .unwrap_or_default()
2473            .to_string_lossy();
2474        let mut manifest = if let Some(filename) = self.dump_file_hash.as_ref() {
2475            Some(match Self::read_manifest(filename) {
2476                Ok(manifest) => manifest,
2477                Err(_) => CxdecDb::default(),
2478            })
2479        } else {
2480            None
2481        };
2482        for i in (0..root_obj.len()).step_by(2) {
2483            let path_hash = PathHash::try_from(
2484                root_obj[i]
2485                    .as_bytes()
2486                    .ok_or_else(|| anyhow::anyhow!("path hash is not bytes."))?,
2487            )?;
2488            if let Some(m) = manifest.as_mut() {
2489                if !m.path_mapping.contains_key(&path_hash) {
2490                    m.path_mapping.insert(path_hash, None);
2491                }
2492            }
2493            let dir_obj = &root_obj[i + 1];
2494            if !dir_obj.is_array() {
2495                anyhow::bail!("dir object at index {} is not array.", i + 1);
2496            }
2497            let (path_name, path_is_hash) = if let Some(n) = self.path_mapping.get(&path_hash) {
2498                (n.to_owned(), false)
2499            } else {
2500                (path_hash.to_string() + "/", true)
2501            };
2502            for j in (0..dir_obj.len()).step_by(2) {
2503                let entry_hash = FileHash::try_from(
2504                    dir_obj[j]
2505                        .as_bytes()
2506                        .ok_or_else(|| anyhow::anyhow!("entry hash is not bytes."))?,
2507                )?;
2508                if let Some(m) = manifest.as_mut() {
2509                    let xp3 = m.file_list.entry(basename.into()).or_default();
2510                    let path = xp3.entry(path_hash).or_default();
2511                    if !path.contains_key(&entry_hash) {
2512                        path.insert(entry_hash, None);
2513                    }
2514                }
2515                let entry_obj = &dir_obj[j + 1];
2516                if !entry_obj.is_array() {
2517                    anyhow::bail!("Entry object at index {},{} is not array.", i + 1, j + 1);
2518                }
2519                if entry_obj.len() < 2 {
2520                    anyhow::bail!("Entry object at index {},{} is too small.", i + 1, j + 1);
2521                }
2522                let entry_id = entry_obj[0]
2523                    .as_u64()
2524                    .ok_or_else(|| anyhow::anyhow!("Entry id is not int."))?;
2525                let entry_key = entry_obj[1]
2526                    .as_u64()
2527                    .ok_or_else(|| anyhow::anyhow!("Entry key is not int."))?;
2528                let (name, name_is_hash) = if let Some(n) = self.file_mapping.get(&entry_hash) {
2529                    (n.to_owned(), false)
2530                } else {
2531                    (entry_hash.to_string(), true)
2532                };
2533                let uname = Self::get_unicode_name(entry_id as u32);
2534                let entry = HxEntry {
2535                    path: path_name.clone(),
2536                    name,
2537                    id: entry_id,
2538                    key: entry_key,
2539                    name_is_hash,
2540                    path_is_hash,
2541                    is_garbage: set.contains(&entry_hash),
2542                };
2543                info_map.insert(uname, entry);
2544            }
2545        }
2546        if let Some(filename) = &self.dump_file_hash
2547            && let Some(db) = manifest
2548        {
2549            serde_json::to_writer_pretty(std::fs::File::create(filename)?, &db)?;
2550        }
2551        Ok(())
2552    }
2553
2554    fn get_unicode_name(mut hash: u32) -> String {
2555        let mut buf = [0u16; 4];
2556        let mut i = 0;
2557        loop {
2558            buf[i] = ((hash & 0x3FFF) + 0x5000) as u16;
2559            hash >>= 14;
2560            i += 1;
2561            if hash == 0 {
2562                break;
2563            }
2564        }
2565        let s = String::from_utf16_lossy(&buf[..i]);
2566        s
2567    }
2568
2569    fn create_filter_key(&self, entry_key: u64, header_key_seed: u64) -> Result<HxFilterKey> {
2570        let key0 = entry_key as u32;
2571        let key1 = (entry_key >> 32) as u32;
2572        let k0 = self.base.execute_xcode(key0)?;
2573        let file_key_0 = (k0.0 as u64) | ((k0.1 as u64) << 32);
2574        let k1 = self.base.execute_xcode(key1)?;
2575        let file_key_1 = (k1.0 as u64) | ((k1.1 as u64) << 32);
2576        let split_position =
2577            (self.base.offset as u64 + ((entry_key >> 16) & self.base.mask as u64)) & 0xffffffff;
2578        let mut header_key = [0u8; 16];
2579        let k3 = self.base.execute_xcode(header_key_seed as u32)?;
2580        let mut v5 = (k3.0 as u64) | ((k3.1 as u64) << 32);
2581        v5 = !v5;
2582        let mut writer = MemWriterRef::new(&mut header_key);
2583        writer.write_u64_be(v5)?;
2584        let k3 = self.base.execute_xcode(v5 as u32)?;
2585        v5 = (k3.0 as u64) | ((k3.1 as u64) << 32);
2586        v5 = !v5;
2587        writer.write_u64_be(v5)?;
2588        Ok(HxFilterKey {
2589            key: [file_key_0, file_key_1],
2590            header_key,
2591            split_position,
2592            has_header_key: true,
2593            flag: false,
2594        })
2595    }
2596}
2597
2598impl AsRef<CxEncryption> for HxCrypt {
2599    fn as_ref(&self) -> &CxEncryption {
2600        &self.base
2601    }
2602}
2603
2604struct CopyStream<'a> {
2605    inner: Box<dyn Read + Send + Sync + 'a>,
2606}
2607
2608impl<'a> CopyStream<'a> {
2609    pub fn new(stream: Box<dyn Read + Send + Sync + 'a>) -> Self {
2610        Self { inner: stream }
2611    }
2612}
2613
2614impl<'a> std::fmt::Debug for CopyStream<'a> {
2615    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2616        f.debug_struct("CopyStream").finish_non_exhaustive()
2617    }
2618}
2619
2620impl<'a> Read for CopyStream<'a> {
2621    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2622        self.inner.read(buf)
2623    }
2624}
2625
2626impl Crypt for HxCrypt {
2627    base_schema_impl!();
2628    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
2629        if let Some(hxv4) = archive.extras.iter().find(|x| x.tag == "Hxv4") {
2630            let mut reader = MemReaderRef::new(&hxv4.data);
2631            let offset = reader.read_u64()? + archive.base_offset;
2632            let size = reader.read_u32()?;
2633            let flags = reader.read_u16()?;
2634            let stream = StreamRegion::with_size(
2635                MutexWrapper::new(archive.inner.clone(), offset),
2636                size as u64,
2637            )?;
2638            self.read_index(stream, flags)?;
2639            let info_map = self.info_map.lock_blocking();
2640            for entry in archive.entries.iter_mut() {
2641                if let Some(info) = info_map.get(&entry.name) {
2642                    if info.is_garbage {
2643                        continue;
2644                    }
2645                    if self.path_hash == PathHashOption::Both || !info.path_is_hash {
2646                        entry.name = format!("{}{}", info.path, info.name);
2647                    } else {
2648                        entry.name = info.name.clone();
2649                    }
2650                    let info = info.clone();
2651                    entry.extra = Some(Arc::new(Box::new(info)))
2652                }
2653            }
2654            archive.entries.retain(|x| {
2655                x.extra.is_some() || !info_map.get(&x.name).is_some_and(|x| x.is_garbage)
2656            });
2657            if self.file_hash == FileHashOption::WithName {
2658                archive.entries.retain(|x| {
2659                    !(x.extra.as_ref().is_some_and(|x| {
2660                        x.as_any()
2661                            .downcast_ref::<HxEntry>()
2662                            .is_some_and(|x| x.name_is_hash)
2663                    }))
2664                });
2665            } else if self.file_hash == FileHashOption::WithoutName {
2666                archive.entries.retain(|x| {
2667                    x.extra.as_ref().is_some_and(|x| {
2668                        x.as_any()
2669                            .downcast_ref::<HxEntry>()
2670                            .is_some_and(|x| x.name_is_hash)
2671                    })
2672                });
2673            }
2674        }
2675        archive.extras.retain(|x| x.tag != "Hxv4");
2676        Ok(())
2677    }
2678    fn decrypt_supported(&self) -> bool {
2679        true
2680    }
2681    fn decrypt_seek_supported(&self) -> bool {
2682        true
2683    }
2684    fn decrypt<'a>(
2685        &self,
2686        entry: &Xp3Entry,
2687        cur_seg: &Segment,
2688        stream: Box<dyn Read + Send + Sync + 'a>,
2689    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2690        let info = match entry.extra.as_ref() {
2691            Some(info) => info,
2692            None => return Ok(Box::new(CopyStream::new(stream))),
2693        };
2694        let info = info
2695            .as_any()
2696            .downcast_ref::<HxEntry>()
2697            .ok_or_else(|| anyhow::anyhow!("extra info is not hx entry."))?;
2698        let mut entry_key = info.key;
2699        if (info.id & 0x100000000) == 0 {
2700            entry_key ^= self.filter_key.qload();
2701        }
2702        let header_key = !entry_key;
2703        let key = self.create_filter_key(entry_key, header_key)?;
2704        let filter = HxFilter::new(key);
2705        let key = (
2706            entry.file_hash,
2707            Box::new(filter) as Box<dyn ICxEncryption + Send + Sync + 'a>,
2708        );
2709        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
2710    }
2711    fn decrypt_with_seek<'a>(
2712        &self,
2713        entry: &Xp3Entry,
2714        cur_seg: &Segment,
2715        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2716    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2717        let info = match entry.extra.as_ref() {
2718            Some(info) => info,
2719            None => return Ok(stream),
2720        };
2721        let info = info
2722            .as_any()
2723            .downcast_ref::<HxEntry>()
2724            .ok_or_else(|| anyhow::anyhow!("extra info is not hx entry."))?;
2725        let mut entry_key = info.key;
2726        if (info.id & 0x100000000) == 0 {
2727            entry_key ^= self.filter_key.qload();
2728        }
2729        let header_key = !entry_key;
2730        let key = self.create_filter_key(entry_key, header_key)?;
2731        let filter = HxFilter::new(key);
2732        let key = (
2733            entry.file_hash,
2734            Box::new(filter) as Box<dyn ICxEncryption + Send + Sync + 'a>,
2735        );
2736        Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
2737    }
2738}
2739
2740#[derive(Debug)]
2741enum TjsValue {
2742    Void,
2743    Str(String),
2744    ByteArray(Vec<u8>),
2745    Int(i64),
2746    #[allow(unused)]
2747    Double(f64),
2748    Array(Vec<TjsValue>),
2749    Dict(HashMap<String, TjsValue>),
2750}
2751
2752impl TjsValue {
2753    fn is_array(&self) -> bool {
2754        matches!(self, Self::Array(_))
2755    }
2756
2757    fn len(&self) -> usize {
2758        match self {
2759            Self::Str(s) => s.len(),
2760            Self::ByteArray(arr) => arr.len(),
2761            Self::Array(arr) => arr.len(),
2762            Self::Dict(dict) => dict.len(),
2763            _ => 0,
2764        }
2765    }
2766
2767    fn as_bytes(&self) -> Option<&[u8]> {
2768        match self {
2769            Self::ByteArray(arr) => Some(arr),
2770            _ => None,
2771        }
2772    }
2773
2774    fn as_u64(&self) -> Option<u64> {
2775        match self {
2776            Self::Int(i) => Some(*i as u64),
2777            _ => None,
2778        }
2779    }
2780}
2781
2782const VOID: TjsValue = TjsValue::Void;
2783
2784impl Index<usize> for TjsValue {
2785    type Output = TjsValue;
2786    fn index(&self, index: usize) -> &Self::Output {
2787        match self {
2788            Self::Array(arr) => arr.get(index).unwrap_or(&VOID),
2789            _ => &VOID,
2790        }
2791    }
2792}
2793
2794fn unpack_string<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<String> {
2795    let len = u32::unpack(reader, big, encoding, &None)? as usize;
2796    let tlen = if encoding.is_utf16le() { len * 2 } else { len };
2797    let mut buf = vec![0u8; tlen];
2798    reader.read_exact(&mut buf)?;
2799    let s = decode_to_string(encoding, &buf, true)?;
2800    Ok(s)
2801}
2802
2803impl StructUnpack for TjsValue {
2804    fn unpack<R: Read + Seek>(
2805        reader: &mut R,
2806        big: bool,
2807        encoding: Encoding,
2808        info: &Option<Box<dyn std::any::Any>>,
2809    ) -> Result<Self> {
2810        let typ = u8::unpack(reader, big, encoding, info)?;
2811        Ok(match typ {
2812            0 => Self::Void,
2813            2 => Self::Str(unpack_string(reader, big, encoding)?),
2814            3 => {
2815                let len = u32::unpack(reader, big, encoding, info)?;
2816                let data = reader.read_exact_vec(len as usize)?;
2817                Self::ByteArray(data)
2818            }
2819            4 => {
2820                let num = i64::unpack(reader, big, encoding, info)?;
2821                Self::Int(num)
2822            }
2823            5 => {
2824                let num = f64::unpack(reader, big, encoding, info)?;
2825                Self::Double(num)
2826            }
2827            0x81 => {
2828                let len = u32::unpack(reader, big, encoding, info)?;
2829                let mut arr = Vec::with_capacity(len as usize);
2830                for _ in 0..len {
2831                    arr.push(Self::unpack(reader, big, encoding, info)?);
2832                }
2833                Self::Array(arr)
2834            }
2835            0xC1 => {
2836                let len = u32::unpack(reader, big, encoding, info)?;
2837                let mut dict = HashMap::with_capacity(len as usize);
2838                for _ in 0..len {
2839                    let name = unpack_string(reader, big, encoding)?;
2840                    let obj = Self::unpack(reader, big, encoding, info)?;
2841                    dict.insert(name, obj);
2842                }
2843                Self::Dict(dict)
2844            }
2845            _ => anyhow::bail!("Unknown type id: {typ:02x}."),
2846        })
2847    }
2848}
2849
2850#[derive(Clone, Debug)]
2851struct HxEntry {
2852    path: String,
2853    name: String,
2854    id: u64,
2855    key: u64,
2856    name_is_hash: bool,
2857    path_is_hash: bool,
2858    is_garbage: bool,
2859}
2860
2861impl AnyDebug for HxEntry {
2862    fn as_any(&self) -> &dyn std::any::Any {
2863        self
2864    }
2865}
2866
2867#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
2868struct HxSplitMix64 {
2869    state: u64,
2870}
2871
2872impl HxSplitMix64 {
2873    pub fn new(seed: u64) -> Self {
2874        Self { state: seed }
2875    }
2876}
2877
2878trait IRng: std::fmt::Debug {
2879    fn next(&mut self) -> u64;
2880}
2881
2882impl IRng for HxSplitMix64 {
2883    fn next(&mut self) -> u64 {
2884        self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
2885        let mut z = self.state;
2886        z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
2887        z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
2888        z ^ (z >> 31)
2889    }
2890}
2891
2892#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2893struct Xoroshiro128PlusPlus {
2894    state: [u64; 2],
2895}
2896
2897impl Xoroshiro128PlusPlus {
2898    pub fn new(state: [u64; 2]) -> Self {
2899        assert!(
2900            state[0] != 0 || state[1] != 0,
2901            "Initial state cannot be all zeros."
2902        );
2903        Self { state }
2904    }
2905}
2906
2907impl IRng for Xoroshiro128PlusPlus {
2908    fn next(&mut self) -> u64 {
2909        let s0 = self.state[0];
2910        let mut s1 = self.state[1];
2911        let result = s0.wrapping_add(s1).rotate_left(17).wrapping_add(s0);
2912        s1 ^= s0;
2913        self.state[0] = s0.rotate_left(49) ^ s1 ^ (s1 << 21);
2914        self.state[1] = s1.rotate_left(28);
2915        result
2916    }
2917}
2918
2919#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2920struct Xoroshiro128StarStar {
2921    state: [u64; 2],
2922}
2923
2924impl Xoroshiro128StarStar {
2925    pub fn new(state: [u64; 2]) -> Self {
2926        assert!(
2927            state[0] != 0 || state[1] != 0,
2928            "Initial state cannot be all zeros."
2929        );
2930        Self { state }
2931    }
2932}
2933
2934impl IRng for Xoroshiro128StarStar {
2935    fn next(&mut self) -> u64 {
2936        let s0 = self.state[0];
2937        let mut s1 = self.state[1];
2938        let result = s0.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
2939        s1 ^= s0;
2940        self.state[0] = s0.rotate_left(24) ^ s1 ^ (s1 << 16);
2941        self.state[1] = s1.rotate_left(37);
2942        result
2943    }
2944}
2945
2946#[derive(Debug)]
2947struct HxProgram {
2948    base: CxProgram,
2949    rng: Box<dyn IRng + Send + Sync>,
2950}
2951
2952impl HxProgram {
2953    pub fn new(seed: u32, control_block: Weak<Vec<u32>>, random_method: i32) -> Self {
2954        let initial_seed = (seed as u64) | (!(seed as u64) << 32);
2955        let mut seeder = HxSplitMix64::new(initial_seed);
2956        let seed1 = seeder.next();
2957        let seed2 = seeder.next();
2958        let xoroshiro_seed = [seed1, seed2];
2959        Self {
2960            base: CxProgram {
2961                code: Vec::new(),
2962                control_block,
2963                length: 0,
2964                seed,
2965            },
2966            rng: if random_method == 0 {
2967                Box::new(Xoroshiro128PlusPlus::new(xoroshiro_seed))
2968            } else {
2969                Box::new(Xoroshiro128StarStar::new(xoroshiro_seed))
2970            },
2971        }
2972    }
2973}
2974
2975impl ICxProgram for HxProgram {
2976    fn execute(&self, hash: u32) -> Result<u32> {
2977        self.base.execute(hash)
2978    }
2979    fn clear(&mut self) {
2980        self.base.clear();
2981    }
2982    fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
2983        self.base.emit(bytecode, length)
2984    }
2985    fn emit_nop(&mut self, count: usize) -> bool {
2986        self.base.emit_nop(count)
2987    }
2988    fn emit_u32(&mut self, x: u32) -> bool {
2989        self.base.emit_u32(x)
2990    }
2991    fn get_random(&mut self) -> u32 {
2992        self.rng.next() as u32
2993    }
2994}
2995
2996#[derive(Debug)]
2997struct HxProgramBuilder {
2998    random_method: i32,
2999}
3000
3001impl HxProgramBuilder {
3002    pub fn new(random_method: i32) -> Self {
3003        Self { random_method }
3004    }
3005}
3006
3007impl ICxProgramBuilder for HxProgramBuilder {
3008    fn build(
3009        &self,
3010        seed: u32,
3011        control_blocks: Weak<Vec<u32>>,
3012    ) -> Box<dyn ICxProgram + Send + Sync> {
3013        Box::new(HxProgram::new(seed, control_blocks, self.random_method))
3014    }
3015}
3016
3017struct HxFilterKey {
3018    key: [u64; 2],
3019    header_key: [u8; 16],
3020    split_position: u64,
3021    has_header_key: bool,
3022    flag: bool,
3023}
3024
3025#[derive(Clone)]
3026struct HxFilterSpanDecryptor {
3027    first_decrypt_key: u32,
3028    key1: u8,
3029    key2: u8,
3030    span_position: [u64; 2],
3031}
3032
3033impl std::fmt::Debug for HxFilterSpanDecryptor {
3034    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3035        f.debug_struct("HxFilterSpanDecryptor")
3036            .field(
3037                "firstDecryptKey",
3038                &format_args!("{:#x}", &self.first_decrypt_key),
3039            )
3040            .field("key1", &format_args!("{:#x}", &self.key1))
3041            .field("key2", &format_args!("{:#x}", &self.key2))
3042            .field(
3043                "spanPosition",
3044                &format_args!("{:#x}, {:#x}", self.span_position[0], self.span_position[1]),
3045            )
3046            .finish()
3047    }
3048}
3049
3050impl HxFilterSpanDecryptor {
3051    pub fn new(key: u64, flag: bool) -> Self {
3052        let decrypt_key_bytes = ((key >> 8) & 0xFFFF) as u32;
3053        let mut first_decrypt_key = (key & 0xFF) as u32;
3054        let mut span_position = [(key >> 48) & 0xFFFF, (key >> 32) & 0xFFFF];
3055        if span_position[0] == span_position[1] {
3056            span_position[1] = span_position[1].wrapping_add(1);
3057        }
3058        if !flag && first_decrypt_key == 0 {
3059            first_decrypt_key = 0xA5;
3060        }
3061        first_decrypt_key = first_decrypt_key.wrapping_mul(0x01010101);
3062        let (key1, key2) = if flag {
3063            (0, 0)
3064        } else {
3065            (
3066                (decrypt_key_bytes & 0xFF) as u8,
3067                ((decrypt_key_bytes >> 8) & 0xFF) as u8,
3068            )
3069        };
3070        Self {
3071            first_decrypt_key,
3072            key1,
3073            key2,
3074            span_position,
3075        }
3076    }
3077
3078    pub fn decrypt(&self, position: u64, data: &mut [u8]) {
3079        if data.is_empty() {
3080            return;
3081        }
3082        let key_bytes = self.first_decrypt_key.to_le_bytes();
3083        for (i, byte) in data.iter_mut().enumerate() {
3084            let key_index = ((position as usize) + i) & 3;
3085            *byte ^= key_bytes[key_index];
3086        }
3087        let data_len = data.len() as u64;
3088        if self.key1 != 0 {
3089            let pos1 = self.span_position[0];
3090            if pos1 >= position && pos1 < position + data_len {
3091                let index = (pos1 - position) as usize;
3092                data[index] ^= self.key1;
3093            }
3094        }
3095        if self.key2 != 0 {
3096            let pos2 = self.span_position[1];
3097            if pos2 >= position && pos2 < position + data_len {
3098                let index = (pos2 - position) as usize;
3099                data[index] ^= self.key2;
3100            }
3101        }
3102    }
3103}
3104
3105struct HxFilter {
3106    span_decryptors: [HxFilterSpanDecryptor; 2],
3107    split_position: u64,
3108    header_key: [u8; 16],
3109    has_header_key: bool,
3110}
3111
3112impl HxFilter {
3113    pub fn new(key: HxFilterKey) -> HxFilter {
3114        HxFilter {
3115            span_decryptors: [
3116                HxFilterSpanDecryptor::new(key.key[0], key.flag),
3117                HxFilterSpanDecryptor::new(key.key[1], key.flag),
3118            ],
3119            split_position: key.split_position,
3120            header_key: key.header_key,
3121            has_header_key: key.has_header_key,
3122        }
3123    }
3124
3125    fn decrypt_header(&self, position: u64, buffer: &mut [u8]) {
3126        let header_len = self.header_key.len() as u64;
3127        let overlap_start = position;
3128        let overlap_end = (position + buffer.len() as u64).min(header_len);
3129        if overlap_start >= overlap_end {
3130            return;
3131        }
3132        for i in overlap_start..overlap_end {
3133            let buffer_index = (i - position) as usize;
3134            let key_index = i as usize;
3135            buffer[buffer_index] ^= self.header_key[key_index];
3136        }
3137    }
3138
3139    pub fn decrypt(&self, position: u64, buffer: &mut [u8]) {
3140        if buffer.is_empty() {
3141            return;
3142        }
3143        if self.has_header_key {
3144            self.decrypt_header(position, buffer);
3145        }
3146        let buffer_len = buffer.len() as u64;
3147        let buffer_end_pos = position + buffer_len;
3148        if buffer_end_pos <= self.split_position {
3149            self.span_decryptors[0].decrypt(position, buffer);
3150        } else if position >= self.split_position {
3151            self.span_decryptors[1].decrypt(position, buffer);
3152        } else {
3153            let split_index = (self.split_position - position) as usize;
3154            let (part1, part2) = buffer.split_at_mut(split_index);
3155            self.span_decryptors[0].decrypt(position, part1);
3156            self.span_decryptors[1].decrypt(self.split_position, part2);
3157        }
3158    }
3159}
3160
3161impl std::fmt::Debug for HxFilter {
3162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3163        f.debug_struct("HxFilter")
3164            .field("spanDecryptors", &self.span_decryptors)
3165            .field(
3166                "splitPosition",
3167                &format_args!("{:#x}", &self.split_position),
3168            )
3169            .field("hasHeaderKey", &self.has_header_key)
3170            .field("headerKey", &hex::encode(self.header_key))
3171            .finish()
3172    }
3173}
3174
3175impl ICxEncryption for HxFilter {
3176    fn get_base_offset(&self, _hash: u32) -> u32 {
3177        0
3178    }
3179    fn decode(
3180        &self,
3181        _key: u32,
3182        _offset: u64,
3183        _buffer: &mut [u8],
3184        _pos: usize,
3185        _count: usize,
3186    ) -> Result<()> {
3187        Ok(())
3188    }
3189    fn inner_decrypt(
3190        &self,
3191        _key: u32,
3192        offset: u64,
3193        buffer: &mut [u8],
3194        pos: usize,
3195        count: usize,
3196    ) -> Result<()> {
3197        self.decrypt(offset, &mut buffer[pos..pos + count]);
3198        Ok(())
3199    }
3200}
3201
3202fn calculate_file_hash(pathname: &str, file_hash_salt: &str) -> FileHash {
3203    use blake2::{Blake2s256, Digest};
3204    let mut hasher = Blake2s256::new();
3205    (pathname.to_lowercase() + file_hash_salt)
3206        .encode_utf16()
3207        .for_each(|b| {
3208            hasher.update(&b.to_le_bytes());
3209        });
3210    let result = hasher.finalize();
3211    let mut hash = [0u8; 32];
3212    hash.copy_from_slice(&result);
3213    FileHash(hash)
3214}
3215
3216fn create_garbage_filename_set(file_hash_salt: &str) -> HashSet<FileHash> {
3217    let mut set = HashSet::new();
3218    set.insert(calculate_file_hash("$$$ This is a protected archive. $$$ 著作者はこのアーカイブが正規の利用方法以外の方法で展開されることを望んでいません。 $$$ This is a protected archive. $$$ 著作者はこのアーカイブが正規の利用方法以外の方法で展開されることを望んでいません。 $$$ This is a protected archive. $$$ 著作者はこのアーカイブが正規の利用方法以外の方法で展開されることを望んでいません。 $$$ Warning! Extracting this archive may infringe on author's rights. 警告 このアーカイブを展開することにより、あなたは著作者の権利を侵害するおそれがあります。.txt", file_hash_salt));
3219    set
3220}
3221
3222fn calculate_path_hash(pathname: &str, path_hash_salt: &str) -> PathHash {
3223    use std::hash::Hasher;
3224    let mut hasher = siphasher::sip::SipHasher24::new();
3225    (pathname.to_lowercase() + path_hash_salt)
3226        .encode_utf16()
3227        .for_each(|b| {
3228            hasher.write(&b.to_le_bytes());
3229        });
3230    let data = hasher.finish().to_ne_bytes();
3231    PathHash(u64::from_be_bytes(data))
3232}
3233
3234fn triple32(mut v: u32) -> u32 {
3235    v ^= v >> 17;
3236    v = v.wrapping_mul(0xED5AD4BB);
3237    v ^= v >> 11;
3238    v = v.wrapping_mul(0xAC4C1B51);
3239    v ^= v >> 15;
3240    v = v.wrapping_mul(0x31848BAB);
3241    v ^= v >> 14;
3242
3243    v
3244}
3245
3246fn fnv_blake(data: &[u8], fnvbase: u32) -> Vec<u8> {
3247    use blake2::{Blake2s256, Digest};
3248    let fnv_value = (0x811C9DC5u32 ^ fnvbase).wrapping_mul(0x01000193);
3249    let mut hash_value = fnv_value;
3250    let mut out = [0u8; 32];
3251    for (i, &byte) in data.iter().enumerate() {
3252        hash_value = triple32(hash_value ^ (byte as u32));
3253        let offset = (i * 4) % 32;
3254        let hash_bytes = hash_value.to_le_bytes();
3255        for j in 0..4 {
3256            out[offset + j] ^= hash_bytes[j];
3257        }
3258    }
3259    let mut hasher = Blake2s256::new();
3260    hasher.update(data);
3261    hasher.update(out);
3262
3263    hasher.finalize().to_vec()
3264}
3265
3266/// Hx table keys
3267#[derive(Clone, Copy, msg_tool_macro::Default)]
3268pub struct HxKeys {
3269    pub key: [u8; 32],
3270    pub nonce_a: [u8; 32],
3271    pub nonce_b: [u8; 32],
3272    /// FilterKey
3273    pub filter: [u8; 8],
3274    /// ControlBlock
3275    #[default([0; 0x1000])]
3276    pub ctrlblk: [u8; 0x1000],
3277}
3278
3279/// Represents the configuration parameters for the WAMSOFT Cx encryption scheme.
3280///
3281/// This structure corresponds to the 0x20-byte memory block found at `ecx + 0x3020`
3282/// (or passed to `CxLoadParams`). It contains the permutation tables for VM instruction
3283/// generation, algorithm selection flags, and encryption constants.
3284#[repr(C)]
3285#[derive(Clone, Copy, Default, StructUnpack)]
3286pub struct CxParams {
3287    /// The permutation table for the "Even" branch of the VM code generation.
3288    /// Maps to `EvenBranchOrder` on GarBRO (Case 0-7).
3289    pub even_branch_perm: [u8; 8],
3290    /// The permutation table for the "Odd" branch of the VM code generation.
3291    /// Maps to `OddBranchOrder` on GarBRO (Case 0-5).
3292    pub odd_branch_perm: [u8; 6],
3293    /// The permutation table for the Prologue of the VM code generation.
3294    /// Maps to `PrologOrder` on GarBRO (Case 0-2).
3295    pub prologue_perm: [u8; 3],
3296    /// Configuration flags.
3297    ///
3298    /// - Bit 7 (0x80): Random Algorithm Type (0 = Xoroshiro128PlusPlus, 1 = Xoroshiro128StarStar).
3299    /// - Bit 0 (0x01): ControlBlock generate mode (0 => NOP, 1 => XOR)
3300    pub flags: u8,
3301    /// The encryption mask used in the key calculation.
3302    pub mask: u16,
3303    /// The encryption offset used in the key calculation.
3304    pub offset: u16,
3305}
3306
3307impl CxParams {
3308    pub fn new(src: &[u8]) -> Result<CxParams> {
3309        let mut reader = MemReaderRef::new(src);
3310        Self::unpack(&mut reader, false, Encoding::Utf8, &None)
3311    }
3312}
3313
3314impl HxKeys {
3315    pub fn new(
3316        bootstrap: &[u8],           // get from bootstrap.tjc
3317        warning: &[u8],             // get from cxdec.tpm
3318        param: &[u8],               // get from cxdec.tpm
3319        uniq: &[u8],                // get from cxdec.tpm
3320        upper_key: Option<[u8; 8]>, // get from cxdec.tpm
3321    ) -> Result<(HxKeys, CxParams)> {
3322        use anyhow::Error;
3323        use argon2::{self, Algorithm, Argon2, Version};
3324        use sha3::Digest;
3325        use shake::digest::XofReader;
3326        // workaround for seed = defaults (if cxdec:word_10081744 == 'f')
3327        // consider make .keyPackages[].key.seed nullable
3328        //
3329        const DEFAULT_SEED: [u8; 8] = [0xCE, 0xEA, 0xAF, 0x2C, 0xEF, 0xBE, 0xAD, 0xDE]; // 0xDEADBEEF2CAFEACEuLL
3330        let upper_key = if let Some(upper_key) = upper_key {
3331            match u64::from_le_bytes(upper_key) == 0 {
3332                true => DEFAULT_SEED,
3333                false => upper_key,
3334            }
3335        } else {
3336            DEFAULT_SEED
3337        };
3338        let params = CxParams::new(param)?; // will returned later for convenience
3339        let bootstrap_and_warning: Vec<u8> = [bootstrap, warning].concat();
3340        let mut hasher = Sha3_224::new();
3341        hasher.update(param);
3342        let params_hash = &hasher.finalize()[..16];
3343        let mut lower_key_full = vec![0u8; 64];
3344
3345        // m=8KB, t=3, p=1, len=64
3346        let argon2 = Argon2::new(
3347            Algorithm::Argon2i,
3348            Version::V0x13,
3349            argon2::Params::new(8, 3, 1, Some(64)).map_err(Error::msg)?,
3350        );
3351        argon2
3352            .hash_password_into(&bootstrap_and_warning, params_hash, &mut lower_key_full)
3353            .map_err(Error::msg)?;
3354        let lower_key = &lower_key_full[..32];
3355        let fnvbase_bytes: [u8; 4] = upper_key[0..4].try_into().map_err(Error::msg)?;
3356        let fnvbase = u32::from_le_bytes(fnvbase_bytes);
3357        let upper_key = fnv_blake(&upper_key, fnvbase);
3358        let b0 = fnv_blake(&bootstrap_and_warning, 0);
3359        let b1 = fnv_blake(param, 1);
3360        let b2 = fnv_blake(uniq, 2);
3361        let mut key_buffer: Vec<u8> = [b0, b1, b2].concat();
3362
3363        if key_buffer.len() < 96 {
3364            return Err(anyhow::anyhow!(
3365                "Concatenated fnv_blake buffers are less than 96 bytes"
3366            ));
3367        }
3368
3369        for i in 0..64 {
3370            key_buffer[i] ^= lower_key[i % 32];
3371        }
3372        for i in 0..32 {
3373            key_buffer[64 + i] ^= upper_key[i];
3374        }
3375
3376        // generate control block
3377        let mut ctrlblk_pa = vec![0u8; 0x1000]; // cx->ControlBlock, partA
3378        let mut ctrlblk_pb = vec![0u8; 0x1000]; // cx->ControlBlock, partB
3379        let mut state = Shake256::default();
3380        shake::digest::Update::update(&mut state, &lower_key_full);
3381        let mut reader = shake::digest::ExtendableOutput::finalize_xof(state);
3382        reader.read(&mut ctrlblk_pa);
3383        reader.read(&mut ctrlblk_pb);
3384        if (params.flags & 1) == 1 {
3385            ctrlblk_pa
3386                .iter_mut()
3387                .zip(ctrlblk_pb.iter())
3388                .for_each(|(dst, src)| {
3389                    *dst ^= *src;
3390                });
3391        }
3392
3393        Ok((
3394            HxKeys {
3395                key: key_buffer[0..32].try_into().map_err(Error::msg)?,
3396                nonce_a: key_buffer[32..64].try_into().map_err(Error::msg)?,
3397                nonce_b: key_buffer[64..96].try_into().map_err(Error::msg)?,
3398                filter: key_buffer[64..72].try_into().map_err(Error::msg)?,
3399                ctrlblk: ctrlblk_pa.try_into().unwrap(),
3400            },
3401            params,
3402        ))
3403    }
3404}
3405
3406const RANDOM_TYPE_FLAG: u8 = 0x80;
3407
3408fn hxkeys_new(
3409    bootstrap: &str,
3410    warning: &str,
3411    param: &[u8],
3412    uniq: &str,
3413    upper_key: Option<[u8; 8]>,
3414) -> Result<(HxKeys, CxParams)> {
3415    let bootstrap = encode_string(Encoding::Utf16LE, bootstrap, true)?;
3416    let warning = encode_string(Encoding::Utf16LE, warning, true)?;
3417    let uniq = encode_string(Encoding::Utf16LE, uniq, true)?;
3418    HxKeys::new(&bootstrap, &warning, param, &uniq, upper_key)
3419}
3420
3421fn map_key_to_garbro(cx: &mut CxParams) {
3422    const S3: [u8; 3] = [0, 1, 2];
3423    const S6: [u8; 6] = [2, 5, 3, 4, 1, 0];
3424    const S8: [u8; 8] = [0, 2, 3, 1, 5, 6, 7, 4];
3425    let mut o3 = [0; 3];
3426    let mut o6 = [0; 6];
3427    let mut o8 = [0; 8];
3428    for i in 0..3 {
3429        o3[cx.prologue_perm[i] as usize] = S3[i];
3430    }
3431    for i in 0..6 {
3432        o6[cx.odd_branch_perm[i] as usize] = S6[i];
3433    }
3434    for i in 0..8 {
3435        o8[cx.even_branch_perm[i] as usize] = S8[i];
3436    }
3437    cx.prologue_perm.copy_from_slice(&o3);
3438    cx.odd_branch_perm.copy_from_slice(&o6);
3439    cx.even_branch_perm.copy_from_slice(&o8);
3440}
3441
3442fn gen_index_keys(key: &HxKeys) -> Result<(IndexKey, IndexKey)> {
3443    let subkeya = hchacha::<chacha20::R20>(&key.key.into(), &Array::try_from(&key.nonce_a[0..16])?);
3444    let subkeyb = hchacha::<chacha20::R20>(&key.key.into(), &Array::try_from(&key.nonce_b[0..16])?);
3445    Ok((
3446        IndexKey {
3447            key: subkeya.into(),
3448            nonce: (&key.nonce_a[16..]).try_into()?,
3449            filter_key: None,
3450        },
3451        IndexKey {
3452            key: subkeyb.into(),
3453            nonce: (&key.nonce_b[16..]).try_into()?,
3454            filter_key: None,
3455        },
3456    ))
3457}
3458
3459#[derive(MyDebug)]
3460pub struct Hxv4Crypt {
3461    base: Mutex<Option<HxCrypt>>,
3462    file_mapping: Arc<HashMap<FileHash, String>>,
3463    path_mapping: Arc<HashMap<PathHash, String>>,
3464    key_packages: Vec<KeyPackage>,
3465    project: String,
3466    #[skip_fmt]
3467    config: ExtraConfig,
3468    filename: String,
3469}
3470
3471impl Hxv4Crypt {
3472    pub fn new(filename: &str, config: &ExtraConfig) -> Result<Self> {
3473        let p = std::path::Path::new(filename);
3474        let b = p
3475            .file_name()
3476            .ok_or_else(|| anyhow::anyhow!("Failed to get file name from path."))?;
3477        let s: &str = &b.to_string_lossy();
3478        let pdir = p
3479            .parent()
3480            .map(|s| s.to_owned())
3481            .filter(|s| !s.as_os_str().is_empty())
3482            .unwrap_or_else(|| PathBuf::from("."));
3483        let filep = get_ignorecase_path(&pdir.join("filelist.json"))?;
3484        let data = match std::fs::read(&filep) {
3485            Ok(data) => data,
3486            Err(err) => {
3487                let keys = load_key_packages_from_exe(&pdir);
3488                if keys.is_empty() {
3489                    return Err(err.into());
3490                }
3491                eprintln!("Loaded {} key packages from game exe.", keys.len());
3492                return Self::new2(&keys, "Unknown", filename, config);
3493            }
3494        };
3495        let data = decode_to_string(Encoding::Utf8, &data, true)?;
3496        let mut manifest = serde_json::from_str::<CxdecDb>(&data)?;
3497        let mut path_map: HashMap<_, _> = manifest
3498            .path_mapping
3499            .iter()
3500            .filter_map(|(k, v)| match v {
3501                Some(v) => Some((k.clone(), v.clone())),
3502                None => None,
3503            })
3504            .collect();
3505        let file_map: HashMap<_, _> = if let Some(s) = manifest.file_list.get(s) {
3506            s.iter()
3507                .map(|s| s.1)
3508                .flatten()
3509                .filter_map(|(k, v)| match v {
3510                    Some(v) => Some((k.clone(), v.clone())),
3511                    None => None,
3512                })
3513                .collect()
3514        } else {
3515            HashMap::new()
3516        };
3517        eprintln!(
3518            "Read {} file entries, {} directory entries and {} key packages from filelist {}.",
3519            file_map.len(),
3520            path_map.len(),
3521            manifest.key_packages.len(),
3522            filep.display()
3523        );
3524        let more_keys = load_key_packages_from_exe(&pdir);
3525        if !more_keys.is_empty() {
3526            eprintln!("Loaded {} key packages from game exe.", more_keys.len());
3527            manifest.key_packages.extend_from_slice(&more_keys);
3528        }
3529        let default_path_hash = calculate_path_hash("", "xp3hnp");
3530        if !path_map.contains_key(&default_path_hash) {
3531            path_map.insert(default_path_hash, String::new());
3532        }
3533        Ok(Self {
3534            base: Mutex::new(None),
3535            file_mapping: Arc::new(file_map),
3536            path_mapping: Arc::new(path_map),
3537            key_packages: manifest.key_packages,
3538            project: manifest.project_name,
3539            config: config.clone(),
3540            filename: filename.to_owned(),
3541        })
3542    }
3543
3544    pub fn new2(
3545        key_packages: &[KeyPackage],
3546        project: &str,
3547        filename: &str,
3548        config: &ExtraConfig,
3549    ) -> Result<Self> {
3550        let p = std::path::Path::new(filename);
3551        let b = p
3552            .file_name()
3553            .ok_or_else(|| anyhow::anyhow!("Failed to get file name from path."))?;
3554        let s: &str = &b.to_string_lossy();
3555        let (file_map, mut path_map) = if let Some(path) = config.xp3_file_list_path.as_ref() {
3556            let data = std::fs::read(path)?;
3557            let data = decode_to_string(Encoding::Utf8, &data, true)?;
3558            HxCrypt::read_names(&data, s)?
3559        } else {
3560            let pdir = p.parent().map(|s| s.to_owned()).unwrap_or_default();
3561            if let Some(k) = HxCrypt::try_default_name(&pdir.join("filelist.json"), s)? {
3562                k
3563            } else if let Some(k) = HxCrypt::try_default_name(&pdir.join("filelist.lst"), s)? {
3564                k
3565            } else {
3566                (HashMap::new(), HashMap::new())
3567            }
3568        };
3569        let default_path_hash = calculate_path_hash("", "xp3hnp");
3570        if !path_map.contains_key(&default_path_hash) {
3571            path_map.insert(default_path_hash, String::new());
3572        }
3573        Ok(Self {
3574            base: Mutex::new(None),
3575            file_mapping: Arc::new(file_map),
3576            path_mapping: Arc::new(path_map),
3577            key_packages: key_packages.to_vec(),
3578            project: project.to_owned(),
3579            config: config.clone(),
3580            filename: filename.to_owned(),
3581        })
3582    }
3583
3584    fn load_package(&self, pack: &KeyPackage, archive: &mut Xp3Archive) -> Result<HxCrypt> {
3585        eprintln!("try key {} for {}", pack.sku, self.project);
3586        let upper_key = match &pack.key.upper_key {
3587            Some(key) => Some(key.as_slice().try_into()?),
3588            None => None,
3589        };
3590        let (key, mut params) = hxkeys_new(
3591            &pack.key.boot_strap,
3592            &pack.key.warning,
3593            &pack.key.params,
3594            &pack.key.archive_unique_key,
3595            upper_key,
3596        )?;
3597        map_key_to_garbro(&mut params);
3598        let (key1, key2) = gen_index_keys(&key)?;
3599        let base = BaseSchema {
3600            hash_after_crypt: false,
3601            startup_tjs_not_encrypted: false,
3602            obfuscated_index: false,
3603        };
3604        let cx = CxSchema {
3605            mask: params.mask as u32,
3606            offset: params.offset as u32,
3607            prolog_order: Base64Bytes {
3608                bytes: params.prologue_perm.to_vec(),
3609            },
3610            odd_branch_order: Base64Bytes {
3611                bytes: params.odd_branch_perm.to_vec(),
3612            },
3613            even_branch_order: Base64Bytes {
3614                bytes: params.even_branch_perm.to_vec(),
3615            },
3616            control_block_name: None,
3617            tpm_file_name: None,
3618        };
3619        let key2 = IndexKeys(vec![key2]);
3620        let filter_key = u64::from_le_bytes(key.filter);
3621        let random_type = if params.flags & RANDOM_TYPE_FLAG != 0 {
3622            1
3623        } else {
3624            0
3625        };
3626        let mut control_block = Vec::with_capacity(0x400);
3627        let mut reader = MemReaderRef::new(&key.ctrlblk);
3628        for _ in 0..0x400 {
3629            control_block.push(!reader.read_u32()?);
3630        }
3631        let crypt = HxCrypt::new_inner(
3632            base,
3633            &cx,
3634            key1,
3635            key2,
3636            filter_key,
3637            random_type,
3638            &self.config,
3639            self.file_mapping.clone(),
3640            self.path_mapping.clone(),
3641            control_block,
3642            &self.filename,
3643        )?;
3644        crypt.init(archive)?;
3645        Ok(crypt)
3646    }
3647}
3648
3649impl Crypt for Hxv4Crypt {
3650    fn startup_tjs_not_encrypted(&self) -> bool {
3651        false
3652    }
3653    fn obfuscated_index(&self) -> bool {
3654        false
3655    }
3656    fn hash_after_crypt(&self) -> bool {
3657        false
3658    }
3659    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
3660        if self.key_packages.len() == 0 {
3661            eprintln!("WARNING: No key package specifed. Decrypt not works.");
3662            crate::COUNTER.inc_warning();
3663            return Ok(());
3664        }
3665        for package in self.key_packages.iter() {
3666            if let Ok(crypt) = self.load_package(package, archive) {
3667                let mut c = self.base.lock_blocking();
3668                c.replace(crypt);
3669                return Ok(());
3670            }
3671        }
3672        Err(anyhow::anyhow!("Failed to decrypt index."))
3673    }
3674    fn decrypt_supported(&self) -> bool {
3675        true
3676    }
3677    fn decrypt_seek_supported(&self) -> bool {
3678        true
3679    }
3680    fn decrypt<'a>(
3681        &self,
3682        entry: &Xp3Entry,
3683        cur_seg: &Segment,
3684        stream: Box<dyn Read + Send + Sync + 'a>,
3685    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
3686        if self.key_packages.len() == 0 {
3687            return Ok(Box::new(CopyStream::new(stream)));
3688        }
3689        let c = self.base.lock_blocking();
3690        let crypt = c
3691            .as_ref()
3692            .ok_or_else(|| anyhow::anyhow!("Archive not inited."))?;
3693        crypt.decrypt(entry, cur_seg, stream)
3694    }
3695    fn decrypt_with_seek<'a>(
3696        &self,
3697        entry: &Xp3Entry,
3698        cur_seg: &Segment,
3699        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
3700    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
3701        if self.key_packages.len() == 0 {
3702            return Ok(stream);
3703        }
3704        let c = self.base.lock_blocking();
3705        let crypt = c
3706            .as_ref()
3707            .ok_or_else(|| anyhow::anyhow!("Archive not inited."))?;
3708        crypt.decrypt_with_seek(entry, cur_seg, stream)
3709    }
3710}
3711
3712// Ported from https://github.com/hktkqj/cxdec-hxv4-static-analysis
3713fn load_key_packages_from_exe<S: AsRef<std::path::Path> + ?Sized>(path: &S) -> Vec<KeyPackage> {
3714    let mut packages = Vec::new();
3715    let (files, _) = match crate::utils::files::collect_ext_files(
3716        &path.as_ref().to_string_lossy(),
3717        false,
3718        &["exe"],
3719    ) {
3720        Ok(f) => f,
3721        Err(_) => {
3722            return packages;
3723        }
3724    };
3725    for file in files {
3726        match load_key_package_from_path(&file) {
3727            Ok(key) => packages.push(key),
3728            Err(_e) => {
3729                // println!("{}\n{}", e, e.backtrace());
3730            }
3731        }
3732    }
3733    packages
3734}
3735
3736fn load_key_package_from_path<S: AsRef<std::path::Path> + ?Sized>(path: &S) -> Result<KeyPackage> {
3737    let view = pelite::FileMap::open(path)?;
3738    let mut last_error = match load_key_package(&view, path) {
3739        Ok(key) => return Ok(key),
3740        Err(e) => e,
3741    };
3742    if libsteamless::is_steamstub(&view) {
3743        let options = libsteamless::SteamlessOptions::default();
3744        match libsteamless::process_data(view.as_ref().to_vec(), &options, &|_level, _message| {}) {
3745            Ok(unpacked) => match load_key_package(&unpacked, path) {
3746                Ok(key) => return Ok(key),
3747                Err(e) => {
3748                    last_error = e;
3749                }
3750            },
3751            Err(err) => {
3752                last_error = err;
3753            }
3754        }
3755    }
3756    let v = view.as_ref();
3757    // DMM DRM may put game exe at the end of protected exe
3758    if let Some(pos) = memchr::memmem::find(&v[4..], &v[..4]) {
3759        let nv = &v[4 + pos..];
3760        match load_key_package(&nv, path) {
3761            Ok(key) => return Ok(key),
3762            Err(e) => {
3763                last_error = e;
3764            }
3765        }
3766    }
3767    Err(last_error)
3768}
3769
3770fn find_resource<
3771    'a,
3772    'b,
3773    D: Into<pelite::resources::Name<'b>>,
3774    N: Into<pelite::resources::Name<'b>>,
3775>(
3776    resources: &pelite::resources::Resources<'a>,
3777    dir: D,
3778    path: N,
3779) -> Result<&'a [u8]> {
3780    use pelite::resources::*;
3781    let base = resources.root()?.get_dir(dir.into())?;
3782    let ent = base.get(path.into())?;
3783    match ent {
3784        Entry::DataEntry(data) => Ok(data.bytes()?),
3785        Entry::Directory(dir) => Ok(dir.first_data()?.bytes()?),
3786    }
3787}
3788
3789/// Minimal PE section info for RVA → file offset mapping.
3790struct PeSections {
3791    image_base: u64,
3792    sections: Vec<(u32, u32, u32, u32)>, // (va, virtual_size, raw, raw_size)
3793}
3794
3795fn parse_pe_sections(data: &[u8]) -> Result<PeSections> {
3796    if data.len() < 0x40 {
3797        anyhow::bail!("PE file too small");
3798    }
3799    let pe_off = u32::from_le_bytes(data[0x3C..0x40].try_into()?) as usize;
3800    if pe_off + 4 > data.len() || &data[pe_off..pe_off + 4] != b"PE\0\0" {
3801        anyhow::bail!("Not a PE file");
3802    }
3803    let coff = pe_off + 4;
3804    let section_count = u16::from_le_bytes(data[coff + 2..coff + 4].try_into()?) as usize;
3805    let optional_size = u16::from_le_bytes(data[coff + 16..coff + 18].try_into()?) as usize;
3806    let optional = coff + 20;
3807    let magic = u16::from_le_bytes(data[optional..optional + 2].try_into()?);
3808    let image_base = if magic == 0x10B {
3809        // PE32
3810        u32::from_le_bytes(data[optional + 28..optional + 32].try_into()?) as u64
3811    } else if magic == 0x20B {
3812        // PE32+
3813        u64::from_le_bytes(data[optional + 24..optional + 32].try_into()?)
3814    } else {
3815        anyhow::bail!("Unsupported PE optional header magic 0x{magic:x}");
3816    };
3817    let section_table = optional + optional_size;
3818    let mut sections = Vec::with_capacity(section_count);
3819    for i in 0..section_count {
3820        let off = section_table + i * 40;
3821        let va = u32::from_le_bytes(data[off + 12..off + 16].try_into()?);
3822        let virtual_size = u32::from_le_bytes(data[off + 8..off + 12].try_into()?);
3823        let raw_size = u32::from_le_bytes(data[off + 16..off + 20].try_into()?);
3824        let raw = u32::from_le_bytes(data[off + 20..off + 24].try_into()?);
3825        let size = virtual_size.max(raw_size);
3826        sections.push((va, size, raw, raw_size));
3827    }
3828    Ok(PeSections {
3829        image_base,
3830        sections,
3831    })
3832}
3833
3834fn rva_to_offset(ps: &PeSections, rva: u32) -> Option<u32> {
3835    for &(va, size, raw, raw_size) in &ps.sections {
3836        if va <= rva && rva < va + size {
3837            let offset = raw + (rva - va);
3838            if offset < raw + raw_size {
3839                return Some(offset);
3840            }
3841        }
3842    }
3843    None
3844}
3845
3846fn va_to_rva(ps: &PeSections, va: u64) -> Option<u32> {
3847    if va >= ps.image_base {
3848        Some((va - ps.image_base) as u32)
3849    } else {
3850        None
3851    }
3852}
3853
3854const BRES_SALT_SIZE: usize = 0x2000;
3855
3856/// Strategy 1: scan x86 code for the adjacent pair
3857///   mov dword ptr [salt_ptr_global], offset salt_bytes
3858///   mov dword ptr [salt_size_global], 2000h
3859fn iter_salt_assignment_candidates(data: &[u8], ps: &PeSections) -> Vec<(u32, Vec<u8>)> {
3860    let mut candidates = Vec::new();
3861    if data.len() < 20 {
3862        return candidates;
3863    }
3864    let mut off = 0usize;
3865    while off + 20 <= data.len() {
3866        if &data[off..off + 2] != b"\xC7\x05" {
3867            off += 1;
3868            continue;
3869        }
3870        if off + 10 > data.len() {
3871            break;
3872        }
3873        let salt_va = u32::from_le_bytes(data[off + 6..off + 10].try_into().unwrap());
3874        if (salt_va as u64) < ps.image_base {
3875            off += 1;
3876            continue;
3877        }
3878        let salt_rva = match va_to_rva(ps, salt_va as u64) {
3879            Some(rva) => rva,
3880            None => {
3881                off += 1;
3882                continue;
3883            }
3884        };
3885        let salt_file_off = match rva_to_offset(ps, salt_rva) {
3886            Some(o) => o as usize,
3887            None => {
3888                off += 1;
3889                continue;
3890            }
3891        };
3892        if salt_file_off + BRES_SALT_SIZE > data.len() {
3893            off += 1;
3894            continue;
3895        }
3896        // Look for the size assignment (0x2000) nearby
3897        let window_end = (off + 64).min(data.len().saturating_sub(10));
3898        for size_off in (off + 10..window_end).step_by(1) {
3899            if &data[size_off..size_off + 2] != b"\xC7\x05" {
3900                continue;
3901            }
3902            let size_val =
3903                u32::from_le_bytes(data[size_off + 6..size_off + 10].try_into().unwrap());
3904            if size_val != BRES_SALT_SIZE as u32 {
3905                continue;
3906            }
3907            let salt = data[salt_file_off..salt_file_off + BRES_SALT_SIZE].to_vec();
3908            candidates.push((salt_file_off as u32, salt));
3909            break;
3910        }
3911        off += 1;
3912    }
3913    candidates
3914}
3915
3916/// Strategy 2: for packed EXEs, locate salt from data markers.
3917fn iter_packed_neighborhood_candidates(data: &[u8]) -> Vec<(u32, Vec<u8>)> {
3918    let mut candidates = Vec::new();
3919    let mut seen = std::collections::HashSet::new();
3920
3921    // Marker "V2Link\0\0" — salt sits immediately before it
3922    let marker_v2 = b"V2Link\x00\x00";
3923    let mut cursor = 0;
3924    while let Some(pos) = data[cursor..]
3925        .windows(marker_v2.len())
3926        .position(|w| w == marker_v2)
3927    {
3928        let anchor = cursor + pos;
3929        if anchor >= BRES_SALT_SIZE {
3930            let salt_off = anchor - BRES_SALT_SIZE;
3931            if salt_off + BRES_SALT_SIZE <= data.len() && seen.insert(salt_off) {
3932                candidates.push((
3933                    salt_off as u32,
3934                    data[salt_off..salt_off + BRES_SALT_SIZE].to_vec(),
3935                ));
3936            }
3937        }
3938        cursor = anchor + 1;
3939    }
3940
3941    // Marker "forcedataxp3\0" — salt starts at 16-byte alignment after it
3942    let marker_fp3 = b"forcedataxp3\x00";
3943    cursor = 0;
3944    while let Some(pos) = data[cursor..]
3945        .windows(marker_fp3.len())
3946        .position(|w| w == marker_fp3)
3947    {
3948        let anchor = cursor + pos;
3949        let window_start = (anchor + marker_fp3.len() + 0xF) & !0xF;
3950        let window_end = (anchor + 0x100).min(data.len().saturating_sub(BRES_SALT_SIZE));
3951        for salt_off in (window_start..=window_end).step_by(0x10) {
3952            if salt_off + BRES_SALT_SIZE <= data.len() && seen.insert(salt_off) {
3953                candidates.push((
3954                    salt_off as u32,
3955                    data[salt_off..salt_off + BRES_SALT_SIZE].to_vec(),
3956                ));
3957            }
3958        }
3959        cursor = anchor + 1;
3960    }
3961
3962    candidates
3963}
3964
3965/// Locate the 8192‑byte bres salt embedded in a PE executable.
3966///
3967/// Mirrors the Python `iter_auto_salt_candidates` + `load_salt` logic from
3968/// the cxdec‑hxv4‑static‑analysis toolchain.
3969fn load_bres_salt<S: AsRef<[u8]> + ?Sized>(data: &S) -> Result<Vec<u8>> {
3970    let data = data.as_ref();
3971    let ps = parse_pe_sections(data)?;
3972
3973    // Strategy 1: code assignments
3974    let candidates = iter_salt_assignment_candidates(data, &ps);
3975    if !candidates.is_empty() {
3976        // println!(
3977        //     "Located bres salt via code assignment (file offset 0x{:x}).",
3978        //     candidates[0].0
3979        // );
3980        return Ok(candidates[0].1.clone());
3981    }
3982
3983    // Strategy 2: packed-neighborhood markers
3984    let candidates = iter_packed_neighborhood_candidates(data);
3985    if !candidates.is_empty() {
3986        // println!(
3987        //     "Located bres salt via data marker (file offset 0x{:x}).",
3988        //     candidates[0].0
3989        // );
3990        return Ok(candidates[0].1.clone());
3991    }
3992
3993    anyhow::bail!("Could not locate 0x{BRES_SALT_SIZE:x}-byte bres salt in PE");
3994}
3995
3996fn decode_bres_root(text: &[u8]) -> Result<String> {
3997    let text = decode_to_string(Encoding::Utf16LE, text, true)?;
3998    let text = text.trim_end_matches('\0');
3999    if !text.starts_with("bres://./") {
4000        anyhow::bail!("Unexpected bres root: {text}");
4001    }
4002    Ok(text[9..].trim_end_matches('/').to_owned())
4003}
4004
4005fn decrypt_bres(data: &[u8], path_key: &str, salt: &[u8]) -> Result<Vec<u8>> {
4006    // generate chacha8 params
4007    use chacha20::ChaCha8;
4008    use chacha20::cipher::{StreamCipher, StreamCipherSeek};
4009    use sha3::{Digest, Sha3_384};
4010    let mut h = Sha3_384::new();
4011    h.update(encode_string(Encoding::Utf16LE, path_key, true)?);
4012    h.update(salt);
4013    let mut digest = MemReader::new(h.finalize().to_vec());
4014    let mut key_bytes = [0u8; 32];
4015    let mut nonce = [0; 2];
4016    digest.read_exact(&mut key_bytes)?;
4017    for k in nonce.iter_mut() {
4018        *k = digest.read_u32()?;
4019    }
4020    let ctr_base = digest.read_u32()?;
4021    let ctr_high = digest.read_u32()?;
4022    let mut data = data.to_vec();
4023    let mut nonce_bytes = [0u8; 12];
4024    nonce_bytes[0..4].copy_from_slice(&ctr_high.to_le_bytes());
4025    for (i, &word) in nonce.iter().enumerate() {
4026        nonce_bytes[4 + i * 4..4 + (i + 1) * 4].copy_from_slice(&word.to_le_bytes());
4027    }
4028    let mut cipher = ChaCha8::new_from_slices(&key_bytes, &nonce_bytes)?;
4029    let chunk_size = 64;
4030    for (bn, chunk) in data.chunks_mut(chunk_size).enumerate() {
4031        let ctr_low = ctr_base ^ (bn as u32);
4032        cipher.seek((ctr_low as u64) * 64);
4033        cipher.apply_keystream(chunk);
4034    }
4035    Ok(data)
4036}
4037
4038fn parse_tjs_strings(data: &[u8]) -> Result<Vec<String>> {
4039    use super::super::super::super::tjs2::*;
4040    if !data.starts_with(b"TJS2100\0") {
4041        anyhow::bail!("invalid tjs2 compiled script.");
4042    }
4043    let mut reader = MemReaderRef::new(data);
4044    reader.pos = 0xC;
4045    let data = DataArea::unpack(&mut reader, false, Encoding::Utf16LE, &None)?;
4046    Ok(data.string_array)
4047}
4048
4049fn find_bootstrap_url<'a>(strings: &'a Vec<String>) -> Result<&'a str> {
4050    for value in strings.iter() {
4051        if value.starts_with("bres://./") && value.to_lowercase().ends_with("/bootstrap") {
4052            return Ok(value.as_str());
4053        }
4054    }
4055    anyhow::bail!("Could not find bootstrap bres URL in STARTUP.TJS strings")
4056}
4057
4058fn bres_key_from_url(url: &str) -> Result<String> {
4059    if !url.starts_with("bres://./") {
4060        anyhow::bail!("Not a local bres URL: {url}");
4061    }
4062    Ok(url[9..]
4063        .split('/')
4064        .next()
4065        .ok_or_else(|| anyhow::anyhow!("No data"))?
4066        .to_owned())
4067}
4068
4069const PARAMS_PAT: &[u8] = b"\0\0\0\0\0\0\0\0PARAMS";
4070const UPKEY_PAT: &[u8] = b"p\0t\0-\0-\0n\0o\0\0\0\0\0";
4071
4072fn parse_config_table(dll: &[u8]) -> Result<HashMap<String, Vec<u8>>> {
4073    let mut ofs =
4074        memchr::memmem::find(dll, PARAMS_PAT).ok_or_else(|| anyhow::anyhow!("No parmas in dll"))?;
4075    ofs += PARAMS_PAT.len() - 6;
4076    let mut reader = MemReaderRef::new(dll);
4077    reader.pos = ofs;
4078    let mut tags = HashMap::new();
4079    loop {
4080        let tag = reader.read_cstring()?;
4081        if tag.as_bytes().is_empty() {
4082            break;
4083        }
4084        let tag = decode_to_string(Encoding::Utf8, tag.as_bytes(), true)?;
4085        let length = reader.read_u16()?;
4086        let value = reader.read_exact_vec(length as usize)?;
4087        tags.insert(tag, value);
4088    }
4089    if let Some(mut ofs) = memchr::memmem::find(dll, UPKEY_PAT) {
4090        ofs += UPKEY_PAT.len();
4091        reader.pos = ofs;
4092        tags.insert("upperKey".into(), reader.read_exact_vec(8)?);
4093    }
4094    Ok(tags)
4095}
4096
4097struct TjsVM<'a> {
4098    file: &'a Tjs2File,
4099    obj: &'a Tjs2Object,
4100    i: usize,
4101    reg: HashMap<i32, String>,
4102}
4103
4104impl<'a> TjsVM<'a> {
4105    fn new(file: &'a Tjs2File, obj: &'a Tjs2Object) -> Self {
4106        Self {
4107            file,
4108            obj,
4109            i: 0,
4110            reg: HashMap::new(),
4111        }
4112    }
4113    fn run(&mut self) -> Result<String> {
4114        let len = self.obj.code.len();
4115        while self.i < len {
4116            if let Some(s) = self.run_step()? {
4117                return Ok(s);
4118            }
4119        }
4120        anyhow::bail!("No _bootStrap calld call invoked.");
4121    }
4122    fn run_step(&mut self) -> Result<Option<String>> {
4123        use tjs2dec::Variant;
4124        use tjs2dec::vmcodes::vm::*;
4125        let code = &self.obj.code;
4126        let op = code[self.i];
4127        const VM_INC1: i32 = VM_INC + 1;
4128        const VM_INC2: i32 = VM_INC + 2;
4129        const VM_INC3: i32 = VM_INC + 3;
4130        const VM_DEC1: i32 = VM_DEC + 1;
4131        const VM_DEC2: i32 = VM_DEC + 2;
4132        const VM_DEC3: i32 = VM_DEC + 3;
4133        match op {
4134            VM_CONST => {
4135                self.ensure(3)?;
4136                let dst_reg = code[self.i + 1];
4137                let src = code[self.i + 2];
4138                if let Some(v) = self.obj.data.get(src as usize) {
4139                    match v {
4140                        Variant::String(idx) => {
4141                            if let Some(s) = self.file.const_pools.strings.get(*idx as usize) {
4142                                self.reg.insert(dst_reg, s.to_owned());
4143                            }
4144                        }
4145                        _ => {}
4146                    }
4147                }
4148                self.i += 3;
4149            }
4150            VM_DELD | VM_TYPEOFD | VM_DELI | VM_TYPEOFI | VM_GPD | VM_GPDS | VM_SPD | VM_SPDE
4151            | VM_SPDEH | VM_SPDS | VM_GPI | VM_GPIS | VM_SPI | VM_SPIE | VM_SPIS | VM_INC1
4152            | VM_INC2 | VM_DEC1 | VM_DEC2 => {
4153                self.skip(4)?;
4154            }
4155            VM_CP | VM_CEQ | VM_CDEQ | VM_CLT | VM_CGT | VM_CHKINS | VM_ADDCI | VM_CHGTHIS
4156            | VM_CCL | VM_ENTRY | VM_SETP | VM_GETP | VM_INC3 | VM_DEC3 => {
4157                self.skip(3)?;
4158            }
4159            VM_CL | VM_SRV | VM_GLOBAL | VM_THROW | VM_TT | VM_TF | VM_SETF | VM_SETNF
4160            | VM_LNOT | VM_BNOT | VM_ASC | VM_CHR | VM_NUM | VM_CHS | VM_INV | VM_CHKINV
4161            | VM_TYPEOF | VM_EVAL | VM_EEXP | VM_INT | VM_REAL | VM_STR | VM_OCTET | VM_JF
4162            | VM_JNF | VM_JMP | VM_INC | VM_DEC => {
4163                self.skip(2)?;
4164            }
4165            VM_RET | VM_NOP | VM_NF | VM_EXTRY | VM_REGMEMBER | VM_DEBUGGER => {
4166                self.skip(1)?;
4167            }
4168            VM_CALL | VM_CALLD | VM_CALLI | VM_NEW => {
4169                let ns = match op {
4170                    VM_CALL | VM_NEW => 4,
4171                    VM_CALLD | VM_CALLI => 5,
4172                    _ => unreachable!(),
4173                };
4174                self.ensure(ns)?;
4175                let i = self.i;
4176                let argc = code[i + ns - 1];
4177                self.i += ns;
4178                if argc == -1 {
4179                    // omit args
4180                } else if argc == -2 {
4181                    // expand args
4182                    self.ensure(1)?;
4183                    let num = code[i + ns] as usize;
4184                    self.i += 1;
4185                    self.ensure(num * 2)?;
4186                    self.i += num * 2;
4187                } else {
4188                    let argc = argc as usize;
4189                    self.ensure(argc)?;
4190                    if op == VM_CALLD {
4191                        let obj = code[i + 2];
4192                        let member = code[i + 3];
4193                        // %proxy
4194                        if obj == -2 {
4195                            if let Some(v) = self.obj.data.get(member as usize) {
4196                                match v {
4197                                    Variant::String(idx) => {
4198                                        if let Some(s) =
4199                                            self.file.const_pools.strings.get(*idx as usize)
4200                                        {
4201                                            if s == "_bootStrap" {
4202                                                if argc >= 1 {
4203                                                    let farg = code[i + 5];
4204                                                    return Ok(Some(
4205                                                        self.reg
4206                                                            .get(&farg)
4207                                                            .ok_or_else(|| {
4208                                                                anyhow::anyhow!(
4209                                                                    "No string in %{}",
4210                                                                    farg
4211                                                                )
4212                                                            })?
4213                                                            .to_owned(),
4214                                                    ));
4215                                                }
4216                                            }
4217                                        }
4218                                    }
4219                                    _ => {}
4220                                }
4221                            }
4222                        }
4223                    }
4224                    self.i += argc;
4225                }
4226            }
4227            _ => {
4228                for base in [
4229                    VM_LOR, VM_LAND, VM_BOR, VM_BXOR, VM_BAND, VM_SAR, VM_SAL, VM_SR, VM_ADD,
4230                    VM_SUB, VM_MOD, VM_DIV, VM_IDIV, VM_MUL,
4231                ] {
4232                    match op - base {
4233                        0 => {
4234                            self.skip(3)?;
4235                            return Ok(None);
4236                        }
4237                        1 | 2 => {
4238                            self.skip(5)?;
4239                            return Ok(None);
4240                        }
4241                        3 => {
4242                            self.skip(4)?;
4243                            return Ok(None);
4244                        }
4245                        _ => {}
4246                    }
4247                }
4248                anyhow::bail!("unknown instruction {}", op);
4249            }
4250        }
4251        Ok(None)
4252    }
4253
4254    fn ensure(&self, need: usize) -> Result<()> {
4255        if self.i + need > self.obj.code.len() {
4256            anyhow::bail!(
4257                "truncated instruction at {}: need {}, code_len {}",
4258                self.i,
4259                need,
4260                self.obj.code.len()
4261            );
4262        }
4263        Ok(())
4264    }
4265
4266    fn skip(&mut self, size: usize) -> Result<()> {
4267        self.ensure(size)?;
4268        self.i += size;
4269        Ok(())
4270    }
4271}
4272
4273fn get_boot_strap(tjs: &[u8]) -> Result<String> {
4274    use tjs2dec::load_tjs2_bytecode;
4275    let file = load_tjs2_bytecode(tjs)?;
4276    let global_obj = file
4277        .objects
4278        .iter()
4279        .find(|s| s.name.as_ref().is_some_and(|s| s == "global"))
4280        .ok_or_else(|| anyhow::anyhow!("No global object."))?;
4281    let mut vm = TjsVM::new(&file, global_obj);
4282    vm.run()
4283}
4284
4285fn load_key_package<S: AsRef<[u8]> + ?Sized, P: AsRef<std::path::Path> + ?Sized>(
4286    data: &S,
4287    path: &P,
4288) -> Result<KeyPackage> {
4289    let bn = path
4290        .as_ref()
4291        .file_stem()
4292        .map(|s| (&s.to_string_lossy()).to_string())
4293        .unwrap_or_else(|| "TBD".into());
4294    let file = PeFile::from_bytes(data)?;
4295    let resources = file.resources()?;
4296    let bootstrap = find_resource(&resources, 10, "BOOTSTRAP")?;
4297    let startup_tjs = find_resource(&resources, 10, "STARTUP.TJS")?;
4298    let text = find_resource(&resources, "TEXT", 127)?;
4299    // println!("Resource loaded.");
4300    let salt = load_bres_salt(data)?;
4301    // println!(
4302    //     "bres_salt={}b",
4303    //     hex::encode(salt.get(..16).unwrap_or_default())
4304    // );
4305    let text = decode_bres_root(text)?;
4306    // println!("bres root: {text}");
4307    let startup_tjs = decrypt_bres(startup_tjs, &text, &salt)?;
4308    let strings = parse_tjs_strings(&startup_tjs)?;
4309    // println!("strings: {strings:#?}");
4310    let bootstrap_url = find_bootstrap_url(&strings)?;
4311    // println!("bootstrap url: {bootstrap_url}");
4312    let bootstrap_key = bres_key_from_url(bootstrap_url)?;
4313    // println!("bootstrap key: {bootstrap_key}");
4314    let bootstrap = decrypt_bres(bootstrap, &bootstrap_key, &salt)?;
4315    // println!(
4316    //     "bootstrap={}b",
4317    //     hex::encode(bootstrap.get(..16).unwrap_or_default())
4318    // );
4319    let mut reader = flate2::read::ZlibDecoder::new(MemReaderRef::new(&bootstrap[8..]));
4320    let mut dll = Vec::new();
4321    reader.read_to_end(&mut dll)?;
4322    if !dll.starts_with(b"MZ") {
4323        anyhow::bail!("Not a dll.");
4324    }
4325    let config = parse_config_table(&dll)?;
4326    let warning = config
4327        .get("WARNING")
4328        .ok_or_else(|| anyhow::anyhow!("WARNING not found"))?;
4329    let warning = decode_to_string(Encoding::Utf8, &warning, true)?;
4330    // println!("warning: {warning}");
4331    let params = config
4332        .get("PARAMS")
4333        .cloned()
4334        .ok_or_else(|| anyhow::anyhow!("PARAMS not found"))?;
4335    // println!("params: {}", hex::encode(&params));
4336    let archive_unique_key = config
4337        .get("UNIQUE")
4338        .ok_or_else(|| anyhow::anyhow!("UNIQUE not found"))?;
4339    let archive_unique_key = decode_to_string(Encoding::Utf16LE, &archive_unique_key, true)?;
4340    // println!("archive_unique_key: {archive_unique_key}");
4341    let upper_key = config.get("upperKey").cloned();
4342    // println!(
4343    //     "upper_key: {:?}",
4344    //     upper_key.as_ref().map(|s| hex::encode(s))
4345    // );
4346    let boot_strap = get_boot_strap(&startup_tjs)?;
4347    // println!("boot_strap: {boot_strap:?}");
4348    Ok(KeyPackage {
4349        description: "TBD".into(),
4350        key: KeyData {
4351            boot_strap,
4352            warning,
4353            params,
4354            archive_unique_key,
4355            upper_key,
4356        },
4357        sku: bn,
4358    })
4359}
4360
4361#[test]
4362fn test_triple32() {
4363    assert_eq!(triple32(0x281ff4b9), 0x3389ba89);
4364    assert_eq!(triple32(0x4899abb), 0xca5cb43b);
4365    assert_eq!(triple32(0x12fb3c7b), 0xe2855413);
4366    assert_eq!(triple32(0x275bdef0), 0x8f95ac52);
4367    assert_eq!(triple32(0x4e9302ee), 0x7b62fdd0);
4368    assert_eq!(triple32(0x4df4823b), 0xe7483578);
4369    assert_eq!(triple32(0x77b0cd89), 0x7d42d107);
4370    assert_eq!(triple32(0x312bebee), 0xa038d73f);
4371    assert_eq!(triple32(0x39203931), 0xf7b87c25);
4372    assert_eq!(triple32(0x633b66f4), 0x98ff988);
4373    assert_eq!(triple32(0x636d1fc1), 0x99897c6);
4374    assert_eq!(triple32(0xb7a114f), 0xef0b8bd3);
4375    assert_eq!(triple32(0x4c7d96c0), 0xc1ba0efe);
4376    assert_eq!(triple32(0x7f26226e), 0x7449b080);
4377    assert_eq!(triple32(0x1bd4bcc7), 0xea9264aa);
4378    assert_eq!(triple32(0x13afe6fd), 0x66396c69);
4379}
4380
4381#[test]
4382fn test_gen_keys() {
4383    let keys = hxkeys_new(
4384        "BOOTSTRAPbootstrap0123456789",
4385        "WARNINGwarning0123456789",
4386        b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a",
4387        "ArchiveUniqueKey0123456789",
4388        Some(*b"\x00\x11\x22\x33\x44\x55\x66\x77"),
4389    )
4390    .unwrap();
4391    let expected_key: [u8; 32] =
4392        hex::decode("4fb07f17eb1d7d0f14fba645e067d5d90a973494f4161da962ee49ccfc9ad237")
4393            .unwrap()
4394            .try_into()
4395            .unwrap();
4396    let expected_nonce_a: [u8; 24] =
4397        hex::decode("c84f47adef9093396d421105bd8893c925b3853aef22d346")
4398            .unwrap()
4399            .try_into()
4400            .unwrap();
4401    let expected_nonce_b: [u8; 24] =
4402        hex::decode("98d9fc0c47eb2684aad17ca33ee8cb1aed30812ee8990500")
4403            .unwrap()
4404            .try_into()
4405            .unwrap();
4406    assert_eq!(keys.0.key, expected_key);
4407    assert_eq!(&keys.0.nonce_a[..24], &expected_nonce_a);
4408    assert_eq!(&keys.0.nonce_b[..24], &expected_nonce_b);
4409}
4410
4411#[test]
4412fn test_real_keys() {
4413    let (key, mut params) = hxkeys_new(
4414        "LimeLightRemonadeJam (C)YUZUSOFT/JUNOS INC. All Rights Reserved.",
4415        "Warning! Extracting this game data may infringe on author's rights.",
4416        b"\x02\x00\x06\x05\x01\x04\x03\x07\x01\x05\x03\x02\x00\x04\x01\x00\x02\x01\xe2\x02\x83\x02",
4417        "{EnaAnjTukRirMikNay}",
4418        Some(*b"\xbf\x22\x36\x8a\x48\x21\x02\x06"),
4419    )
4420    .unwrap();
4421    map_key_to_garbro(&mut params);
4422    assert_eq!(params.mask, 738);
4423    assert_eq!(params.offset, 643);
4424    use base64::Engine;
4425    let b64 = base64::engine::general_purpose::STANDARD;
4426    assert_eq!(b64.encode(params.prologue_perm), "AQAC");
4427    assert_eq!(b64.encode(params.odd_branch_perm), "AQIEAwAF");
4428    assert_eq!(b64.encode(params.even_branch_perm), "AgUABwYBAwQ=");
4429    assert_eq!(u64::from_le_bytes(key.filter), 13089994567570788352);
4430    let (ind1, ind2) = gen_index_keys(&key).unwrap();
4431    assert_eq!(
4432        b64.encode(&ind1.key),
4433        "fMktWafCUSPGVDvR/8LUx9f+yh3Y+PIq90XmzJ6xZhI="
4434    );
4435    assert_eq!(b64.encode(&ind1.nonce), "aDnPYFowPzhVdOsfJweaYA==");
4436    assert_eq!(
4437        b64.encode(&ind2.key),
4438        "kHMdDweFjeDFVTwQA10XQAPUAOfXzKp2ukygeLzGzHg="
4439    );
4440    assert_eq!(b64.encode(&ind2.nonce), "CSBjzSXQNTioPhDp710WCQ==");
4441    assert!((params.flags & RANDOM_TYPE_FLAG) == 0);
4442}
4443
4444#[test]
4445fn test_filehash_deserialize() {
4446    assert_eq!(
4447        FileHash([
4448            0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0,
4449            0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
4450        ]),
4451        serde_json::from_str(
4452            "\"000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0F\""
4453        )
4454        .unwrap()
4455    );
4456}
4457
4458#[test]
4459fn test_calculate_path_hash() {
4460    assert_eq!(
4461        calculate_path_hash("", "xp3hnp"),
4462        PathHash::try_from("94d4a97c61498621").unwrap(),
4463    );
4464    assert_eq!(
4465        calculate_path_hash("scenario/scripts/", "xp3hnp"),
4466        PathHash::try_from("c81c19411c1a5e54").unwrap(),
4467    );
4468}